Repository: ageerle/ruoyi-admin Branch: main Commit: 3c6e6692be38 Files: 1431 Total size: 4.1 MB Directory structure: gitextract_bqdxkhyg/ ├── .browserslistrc ├── .commitlintrc.js ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .gitconfig ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.mjs ├── .stylelintignore ├── LICENSE ├── README.md ├── apps/ │ └── web-antd/ │ ├── Dockerfile │ ├── docker-compose.yml │ ├── docker-entrypoint.sh │ ├── index.html │ ├── nginx.conf │ ├── package.json │ ├── postcss.config.mjs │ ├── public/ │ │ └── tinymce/ │ │ ├── langs/ │ │ │ ├── README.md │ │ │ └── zh_CN.js │ │ ├── plugins/ │ │ │ ├── emoticons/ │ │ │ │ └── js/ │ │ │ │ ├── emojiimages.js │ │ │ │ └── emojis.js │ │ │ └── help/ │ │ │ └── js/ │ │ │ └── i18n/ │ │ │ └── keynav/ │ │ │ ├── ar.js │ │ │ ├── bg_BG.js │ │ │ ├── ca.js │ │ │ ├── cs.js │ │ │ ├── da.js │ │ │ ├── de.js │ │ │ ├── el.js │ │ │ ├── en.js │ │ │ ├── es.js │ │ │ ├── eu.js │ │ │ ├── fa.js │ │ │ ├── fi.js │ │ │ ├── fr_FR.js │ │ │ ├── he_IL.js │ │ │ ├── hi.js │ │ │ ├── hr.js │ │ │ ├── hu_HU.js │ │ │ ├── id.js │ │ │ ├── it.js │ │ │ ├── ja.js │ │ │ ├── kk.js │ │ │ ├── ko_KR.js │ │ │ ├── ms.js │ │ │ ├── nb_NO.js │ │ │ ├── nl.js │ │ │ ├── pl.js │ │ │ ├── pt_BR.js │ │ │ ├── pt_PT.js │ │ │ ├── ro.js │ │ │ ├── ru.js │ │ │ ├── sk.js │ │ │ ├── sl_SI.js │ │ │ ├── sv_SE.js │ │ │ ├── th_TH.js │ │ │ ├── tr.js │ │ │ ├── uk.js │ │ │ ├── vi.js │ │ │ ├── zh_CN.js │ │ │ └── zh_TW.js │ │ ├── skins/ │ │ │ ├── content/ │ │ │ │ ├── dark/ │ │ │ │ │ └── content.js │ │ │ │ ├── default/ │ │ │ │ │ └── content.js │ │ │ │ ├── document/ │ │ │ │ │ └── content.js │ │ │ │ ├── tinymce-5/ │ │ │ │ │ └── content.js │ │ │ │ ├── tinymce-5-dark/ │ │ │ │ │ └── content.js │ │ │ │ └── writer/ │ │ │ │ └── content.js │ │ │ └── ui/ │ │ │ ├── oxide/ │ │ │ │ ├── content.inline.js │ │ │ │ ├── content.js │ │ │ │ ├── skin.js │ │ │ │ └── skin.shadowdom.js │ │ │ ├── oxide-dark/ │ │ │ │ ├── content.inline.js │ │ │ │ ├── content.js │ │ │ │ ├── skin.js │ │ │ │ └── skin.shadowdom.js │ │ │ ├── tinymce-5/ │ │ │ │ ├── content.inline.js │ │ │ │ ├── content.js │ │ │ │ ├── skin.js │ │ │ │ └── skin.shadowdom.js │ │ │ └── tinymce-5-dark/ │ │ │ ├── content.inline.js │ │ │ ├── content.js │ │ │ ├── skin.js │ │ │ └── skin.shadowdom.js │ │ └── tinymce.d.ts │ ├── src/ │ │ ├── adapter/ │ │ │ ├── component/ │ │ │ │ └── index.ts │ │ │ ├── form.ts │ │ │ └── vxe-table.ts │ │ ├── api/ │ │ │ ├── aiflow/ │ │ │ │ ├── API文档.md │ │ │ │ ├── adapters.ts │ │ │ │ ├── index.ts │ │ │ │ ├── runtime.ts │ │ │ │ └── types.d.ts │ │ │ ├── chat/ │ │ │ │ ├── chatconfig/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── message/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── model/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ └── provider/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── common.d.ts │ │ │ ├── core/ │ │ │ │ ├── auth.ts │ │ │ │ ├── captcha.ts │ │ │ │ ├── index.ts │ │ │ │ ├── menu.ts │ │ │ │ ├── upload.ts │ │ │ │ └── user.ts │ │ │ ├── graph/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── helper.ts │ │ │ ├── index.ts │ │ │ ├── knowledge/ │ │ │ │ ├── attach/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── fragment/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ └── info/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── mcp/ │ │ │ │ ├── market/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ └── tool/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── monitor/ │ │ │ │ ├── cache/ │ │ │ │ │ └── index.ts │ │ │ │ ├── logininfo/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── online/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ └── operlog/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── operator/ │ │ │ │ └── configurationManage/ │ │ │ │ └── index.ts │ │ │ ├── request.ts │ │ │ ├── system/ │ │ │ │ ├── client/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── config/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── dept/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── dict/ │ │ │ │ │ ├── dict-data-model.d.ts │ │ │ │ │ ├── dict-data.ts │ │ │ │ │ ├── dict-type-model.d.ts │ │ │ │ │ ├── dict-type.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── menu/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── notice/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── oss/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── oss-config/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── post/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── profile/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── role/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── social/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── tenant/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ ├── tenant-package/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── model.d.ts │ │ │ │ └── user/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── tool/ │ │ │ │ └── gen/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ └── workflow/ │ │ │ ├── category/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── definition/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── instance/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── spel/ │ │ │ │ ├── index.tsx │ │ │ │ └── model.d.ts │ │ │ └── task/ │ │ │ ├── index.ts │ │ │ └── model.d.ts │ │ ├── app.vue │ │ ├── bootstrap.ts │ │ ├── components/ │ │ │ ├── cropper/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── cropper-avatar.vue │ │ │ │ ├── cropper-modal.vue │ │ │ │ ├── cropper.vue │ │ │ │ └── typing.ts │ │ │ ├── description/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── description.vue │ │ │ │ ├── typing.ts │ │ │ │ └── useDescription.ts │ │ │ ├── dict/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── type.d.ts │ │ │ ├── global/ │ │ │ │ ├── button.ts │ │ │ │ ├── index.ts │ │ │ │ └── slot.ts │ │ │ ├── table/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── options-tag.vue │ │ │ │ └── table-switch.vue │ │ │ ├── tenant-toggle/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ └── index.vue │ │ │ ├── tinymce/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── editor.vue │ │ │ │ ├── helper.ts │ │ │ │ └── tinymce.ts │ │ │ ├── tree/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── data.tsx │ │ │ │ ├── helper.tsx │ │ │ │ ├── hook.tsx │ │ │ │ ├── menu-select-table.vue │ │ │ │ └── tree-select-panel.vue │ │ │ ├── upload/ │ │ │ │ ├── index.ts │ │ │ │ └── src/ │ │ │ │ ├── file-upload.vue │ │ │ │ ├── helper.ts │ │ │ │ ├── hook.ts │ │ │ │ ├── image-upload.vue │ │ │ │ ├── note.md │ │ │ │ └── props.d.ts │ │ │ └── upload-old/ │ │ │ ├── index.ts │ │ │ └── src/ │ │ │ ├── file-upload.vue │ │ │ ├── helper.ts │ │ │ ├── image-upload.vue │ │ │ ├── typing.ts │ │ │ └── use-upload.ts │ │ ├── layouts/ │ │ │ ├── auth.vue │ │ │ ├── basic.vue │ │ │ └── index.ts │ │ ├── locales/ │ │ │ ├── README.md │ │ │ ├── index.ts │ │ │ └── langs/ │ │ │ ├── en-US/ │ │ │ │ ├── component.json │ │ │ │ ├── demos.json │ │ │ │ ├── http.json │ │ │ │ ├── menu.json │ │ │ │ ├── page.json │ │ │ │ └── pages.json │ │ │ └── zh-CN/ │ │ │ ├── component.json │ │ │ ├── demos.json │ │ │ ├── http.json │ │ │ ├── menu.json │ │ │ ├── page.json │ │ │ └── pages.json │ │ ├── main.ts │ │ ├── packages/ │ │ │ └── workflow-designer/ │ │ │ ├── StandaloneWorkflowDesigner.vue │ │ │ ├── components/ │ │ │ │ ├── CommonNodeHeader.vue │ │ │ │ ├── RunDetail.vue │ │ │ │ ├── RuntimeNodes.vue │ │ │ │ ├── SvgIcon.vue │ │ │ │ ├── WfVariableSelector.vue │ │ │ │ ├── edges/ │ │ │ │ │ ├── CustomEdge.vue │ │ │ │ │ ├── CustomEdge2.vue │ │ │ │ │ └── SpecialEdge.vue │ │ │ │ └── nodes/ │ │ │ │ ├── AnswerNode.vue │ │ │ │ ├── ClassifierNode.vue │ │ │ │ ├── Dalle3Node.vue │ │ │ │ ├── DocumentExtractorNode.vue │ │ │ │ ├── EndNode.vue │ │ │ │ ├── FaqExtractorNode.vue │ │ │ │ ├── GoogleNode.vue │ │ │ │ ├── HumanFeedbackNode.vue │ │ │ │ ├── KeywordExtractorNode.vue │ │ │ │ ├── NodeShell.vue │ │ │ │ ├── StartNode.vue │ │ │ │ ├── SwitcherNode.vue │ │ │ │ ├── TemplateNode.vue │ │ │ │ ├── TestNode.vue │ │ │ │ └── TongyiwanxNode.vue │ │ │ ├── docs/ │ │ │ │ ├── README.md │ │ │ │ ├── WorkflowDesigner树状架构分析.md │ │ │ │ ├── WorkflowDesigner详细树状架构.md │ │ │ │ ├── 图标说明.md │ │ │ │ └── 流程编排架构说明.md │ │ │ ├── index.ts │ │ │ ├── panels/ │ │ │ │ └── RightPanel.vue │ │ │ ├── properties/ │ │ │ │ ├── AnswerNodeProperty.vue │ │ │ │ ├── ClassifierNodeProperty.vue │ │ │ │ ├── GenericNodeProperty.vue │ │ │ │ ├── KeywordExtractorNodeProperty.vue │ │ │ │ ├── StartNodeProperty.vue │ │ │ │ ├── SwitcherNodeProperty.vue │ │ │ │ ├── TestNodeProperty.vue │ │ │ │ └── defaults.ts │ │ │ ├── store/ │ │ │ │ └── index.ts │ │ │ ├── types/ │ │ │ │ └── index.d.ts │ │ │ └── utils/ │ │ │ ├── operators.ts │ │ │ └── workflow-util.ts │ │ ├── preferences.ts │ │ ├── router/ │ │ │ ├── access.ts │ │ │ ├── guard.ts │ │ │ ├── index.ts │ │ │ └── routes/ │ │ │ ├── core.ts │ │ │ ├── index.ts │ │ │ ├── local.ts │ │ │ └── modules/ │ │ │ ├── aiflow.ts │ │ │ ├── dashboard.ts │ │ │ ├── knowledge.ts │ │ │ └── vben.ts │ │ ├── store/ │ │ │ ├── auth.ts │ │ │ ├── dict.ts │ │ │ ├── index.ts │ │ │ ├── notify.ts │ │ │ └── tenant.ts │ │ ├── upload-tip.ts │ │ ├── utils/ │ │ │ ├── dict.ts │ │ │ ├── file/ │ │ │ │ ├── base64Conver.ts │ │ │ │ ├── download.ts │ │ │ │ └── index.ts │ │ │ ├── message.ts │ │ │ ├── modal.tsx │ │ │ ├── popup.ts │ │ │ └── render.tsx │ │ └── views/ │ │ ├── _core/ │ │ │ ├── README.md │ │ │ ├── about/ │ │ │ │ └── index.vue │ │ │ ├── authentication/ │ │ │ │ ├── code-login.vue │ │ │ │ ├── forget-password.vue │ │ │ │ ├── login.vue │ │ │ │ ├── oauth-login.vue │ │ │ │ ├── qrcode-login.vue │ │ │ │ └── register.vue │ │ │ ├── fallback/ │ │ │ │ ├── coming-soon.vue │ │ │ │ ├── forbidden.vue │ │ │ │ ├── internal-error.vue │ │ │ │ ├── not-found.vue │ │ │ │ └── offline.vue │ │ │ ├── oauth-common.ts │ │ │ ├── profile/ │ │ │ │ ├── components/ │ │ │ │ │ ├── account-bind.vue │ │ │ │ │ ├── base-setting.vue │ │ │ │ │ ├── online-device.vue │ │ │ │ │ └── secure-setting.vue │ │ │ │ ├── index.vue │ │ │ │ ├── mitt.ts │ │ │ │ ├── profile-panel.vue │ │ │ │ └── setting-panel.vue │ │ │ └── social-callback/ │ │ │ └── index.vue │ │ ├── aiflow/ │ │ │ ├── README.md │ │ │ ├── data.ts │ │ │ ├── edit.vue │ │ │ ├── index.vue │ │ │ ├── run.vue │ │ │ └── workflow-modal.vue │ │ ├── chat/ │ │ │ ├── message/ │ │ │ │ ├── data.ts │ │ │ │ ├── index.vue │ │ │ │ └── message-modal.vue │ │ │ ├── model/ │ │ │ │ ├── data.ts │ │ │ │ ├── index.vue │ │ │ │ └── model-modal.vue │ │ │ └── provider/ │ │ │ ├── data.ts │ │ │ ├── index.vue │ │ │ ├── options.ts │ │ │ └── provider-modal.vue │ │ ├── dashboard/ │ │ │ ├── analytics/ │ │ │ │ ├── analytics-trends.vue │ │ │ │ ├── analytics-visits-data.vue │ │ │ │ ├── analytics-visits-sales.vue │ │ │ │ ├── analytics-visits-source.vue │ │ │ │ ├── analytics-visits.vue │ │ │ │ └── index.vue │ │ │ └── workspace/ │ │ │ └── index.vue │ │ ├── knowledge/ │ │ │ ├── attach/ │ │ │ │ ├── attach-modal.vue │ │ │ │ ├── data.ts │ │ │ │ └── index.vue │ │ │ ├── fragment/ │ │ │ │ ├── data.ts │ │ │ │ ├── fragment-modal.vue │ │ │ │ └── index.vue │ │ │ └── info/ │ │ │ ├── components/ │ │ │ │ └── KnowledgeAddModal.vue │ │ │ ├── data.ts │ │ │ ├── detail/ │ │ │ │ ├── components/ │ │ │ │ │ ├── FileManagement.vue │ │ │ │ │ ├── KnowledgeConfig.vue │ │ │ │ │ └── RetrievalTest.vue │ │ │ │ └── index.vue │ │ │ ├── index.vue │ │ │ ├── info-drawer.vue │ │ │ └── info-modal.vue │ │ ├── mcp/ │ │ │ ├── market/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── market-drawer.vue │ │ │ └── tool/ │ │ │ ├── data.tsx │ │ │ ├── index.vue │ │ │ └── tool-drawer.vue │ │ ├── monitor/ │ │ │ ├── admin/ │ │ │ │ └── index.vue │ │ │ ├── cache/ │ │ │ │ ├── components/ │ │ │ │ │ ├── command-chart.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── memory-chart.vue │ │ │ │ │ └── redis-description.vue │ │ │ │ └── index.vue │ │ │ ├── logininfor/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── login-info-modal.vue │ │ │ ├── online/ │ │ │ │ ├── data.ts │ │ │ │ └── index.vue │ │ │ ├── operlog/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── operation-preview-drawer.vue │ │ │ └── snailjob/ │ │ │ └── index.vue │ │ ├── nodeManage/ │ │ │ ├── data.ts │ │ │ ├── index.d.ts │ │ │ ├── index.vue │ │ │ └── modal.vue │ │ ├── system/ │ │ │ ├── client/ │ │ │ │ ├── client-drawer.vue │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── secret-input.vue │ │ │ ├── config/ │ │ │ │ ├── config-modal.vue │ │ │ │ ├── data.ts │ │ │ │ └── index.vue │ │ │ ├── dept/ │ │ │ │ ├── data.ts │ │ │ │ ├── dept-drawer.vue │ │ │ │ └── index.vue │ │ │ ├── dict/ │ │ │ │ ├── data/ │ │ │ │ │ ├── data.ts │ │ │ │ │ ├── dict-data-drawer.vue │ │ │ │ │ ├── index.vue │ │ │ │ │ └── tag-style-picker.vue │ │ │ │ ├── data.vue │ │ │ │ ├── index.vue │ │ │ │ ├── mitt.ts │ │ │ │ └── type/ │ │ │ │ ├── data.ts │ │ │ │ ├── dict-type-modal.vue │ │ │ │ ├── index-refactor.vue │ │ │ │ └── index.vue │ │ │ ├── menu/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── menu-drawer.vue │ │ │ ├── notice/ │ │ │ │ ├── data.ts │ │ │ │ ├── index.vue │ │ │ │ └── notice-modal.vue │ │ │ ├── oss/ │ │ │ │ ├── config.vue │ │ │ │ ├── constant.ts │ │ │ │ ├── data.tsx │ │ │ │ ├── fallback-image.txt │ │ │ │ ├── file-upload-modal.vue │ │ │ │ ├── image-upload-modal.vue │ │ │ │ └── index.vue │ │ │ ├── oss-config/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── oss-config-drawer.vue │ │ │ ├── post/ │ │ │ │ ├── data.ts │ │ │ │ ├── index.vue │ │ │ │ └── post-drawer.vue │ │ │ ├── role/ │ │ │ │ ├── authUser.vue │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ ├── role-auth-modal.vue │ │ │ │ └── role-drawer.vue │ │ │ ├── role-assign/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── role-assign-drawer.vue │ │ │ ├── tenant/ │ │ │ │ ├── data.tsx │ │ │ │ ├── index.vue │ │ │ │ └── tenant-drawer.vue │ │ │ ├── tenantPackage/ │ │ │ │ ├── data.ts │ │ │ │ ├── index.vue │ │ │ │ └── tenant-package-drawer.vue │ │ │ └── user/ │ │ │ ├── authRole.vue │ │ │ ├── data.tsx │ │ │ ├── dept-tree.vue │ │ │ ├── index.vue │ │ │ ├── user-drawer.vue │ │ │ ├── user-import-modal.vue │ │ │ ├── user-info-modal.vue │ │ │ └── user-reset-pwd-modal.vue │ │ ├── tool/ │ │ │ └── gen/ │ │ │ ├── code-preview-modal.vue │ │ │ ├── data.tsx │ │ │ ├── edit-gen.vue │ │ │ ├── edit-steps/ │ │ │ │ ├── basic-setting.vue │ │ │ │ ├── basic.tsx │ │ │ │ ├── gen-config.vue │ │ │ │ ├── gen-data.tsx │ │ │ │ └── index.ts │ │ │ ├── editTable.vue │ │ │ ├── index.vue │ │ │ └── table-import-modal.vue │ │ └── workflow/ │ │ ├── category/ │ │ │ ├── category-modal.vue │ │ │ ├── data.ts │ │ │ └── index.vue │ │ ├── components/ │ │ │ ├── actions/ │ │ │ │ ├── flow-actions.vue │ │ │ │ └── index.ts │ │ │ ├── apply-modal.vue │ │ │ ├── approval-card.vue │ │ │ ├── approval-content.vue │ │ │ ├── approval-details.vue │ │ │ ├── approval-modal.vue │ │ │ ├── approval-panel.vue │ │ │ ├── approval-rejection-modal.vue │ │ │ ├── approval-timeline-item.vue │ │ │ ├── approval-timeline.vue │ │ │ ├── copy-component.vue │ │ │ ├── flow-designer.vue │ │ │ ├── flow-info-modal.vue │ │ │ ├── flow-interfere-modal.vue │ │ │ ├── flow-preview.vue │ │ │ ├── helper.tsx │ │ │ ├── hook.ts │ │ │ ├── index.ts │ │ │ ├── type.d.ts │ │ │ └── user-select-modal.vue │ │ ├── leave/ │ │ │ ├── api/ │ │ │ │ ├── index.ts │ │ │ │ └── model.d.ts │ │ │ ├── data.tsx │ │ │ ├── hook.ts │ │ │ ├── index.vue │ │ │ ├── leave-description.vue │ │ │ ├── leave-drawer.vue │ │ │ ├── leave-form.vue │ │ │ └── leaveEdit.vue │ │ ├── processDefinition/ │ │ │ ├── category-tree.vue │ │ │ ├── constant.ts │ │ │ ├── data.tsx │ │ │ ├── design.vue │ │ │ ├── index.vue │ │ │ ├── process-definition-deploy-modal.vue │ │ │ └── process-definition-modal.vue │ │ ├── processInstance/ │ │ │ ├── data.tsx │ │ │ ├── index.vue │ │ │ ├── instance-invalid-modal.vue │ │ │ └── instance-variable-modal.vue │ │ ├── register.ts │ │ ├── spel/ │ │ │ ├── common.ts │ │ │ ├── data.tsx │ │ │ ├── index.vue │ │ │ ├── spel-drawer.vue │ │ │ └── spel-previewer.vue │ │ └── task/ │ │ ├── allTaskWaiting.vue │ │ ├── constant.ts │ │ ├── myDocument.vue │ │ ├── taskCopyList.vue │ │ ├── taskFinish.vue │ │ └── taskWaiting.vue │ ├── tailwind.config.mjs │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── types/ │ │ ├── directive.d.ts │ │ └── global-components.d.ts │ └── vite.config.mts ├── docker-compose-all.yaml ├── eslint.config.mjs ├── internal/ │ ├── lint-configs/ │ │ ├── commitlint-config/ │ │ │ ├── index.mjs │ │ │ └── package.json │ │ ├── eslint-config/ │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── configs/ │ │ │ │ │ ├── command.ts │ │ │ │ │ ├── comments.ts │ │ │ │ │ ├── disableds.ts │ │ │ │ │ ├── ignores.ts │ │ │ │ │ ├── import.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── javascript.ts │ │ │ │ │ ├── jsdoc.ts │ │ │ │ │ ├── jsonc.ts │ │ │ │ │ ├── node.ts │ │ │ │ │ ├── perfectionist.ts │ │ │ │ │ ├── prettier.ts │ │ │ │ │ ├── regexp.ts │ │ │ │ │ ├── test.ts │ │ │ │ │ ├── turbo.ts │ │ │ │ │ ├── typescript.ts │ │ │ │ │ ├── unicorn.ts │ │ │ │ │ └── vue.ts │ │ │ │ ├── custom-config.ts │ │ │ │ ├── index.ts │ │ │ │ └── util.ts │ │ │ └── tsconfig.json │ │ ├── prettier-config/ │ │ │ ├── index.mjs │ │ │ └── package.json │ │ └── stylelint-config/ │ │ ├── index.mjs │ │ └── package.json │ ├── node-utils/ │ │ ├── build.config.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── __tests__/ │ │ │ │ ├── hash.test.ts │ │ │ │ └── path.test.ts │ │ │ ├── constants.ts │ │ │ ├── date.ts │ │ │ ├── fs.ts │ │ │ ├── git.ts │ │ │ ├── hash.ts │ │ │ ├── index.ts │ │ │ ├── monorepo.ts │ │ │ ├── path.ts │ │ │ ├── prettier.ts │ │ │ └── spinner.ts │ │ └── tsconfig.json │ ├── tailwind-config/ │ │ ├── build.config.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── module.d.ts │ │ │ ├── plugins/ │ │ │ │ └── entry.ts │ │ │ └── postcss.config.ts │ │ └── tsconfig.json │ ├── tsconfig/ │ │ ├── base.json │ │ ├── library.json │ │ ├── node.json │ │ ├── package.json │ │ ├── web-app.json │ │ └── web.json │ └── vite-config/ │ ├── build.config.ts │ ├── package.json │ ├── src/ │ │ ├── config/ │ │ │ ├── application.ts │ │ │ ├── common.ts │ │ │ ├── index.ts │ │ │ └── library.ts │ │ ├── index.ts │ │ ├── options.ts │ │ ├── plugins/ │ │ │ ├── archiver.ts │ │ │ ├── extra-app-config.ts │ │ │ ├── importmap.ts │ │ │ ├── index.ts │ │ │ ├── inject-app-loading/ │ │ │ │ ├── README.md │ │ │ │ ├── default-loading-antd.html │ │ │ │ ├── default-loading.html │ │ │ │ └── index.ts │ │ │ ├── inject-metadata.ts │ │ │ ├── license.ts │ │ │ ├── nitro-mock.ts │ │ │ ├── print.ts │ │ │ └── vxe-table.ts │ │ ├── typing.ts │ │ └── utils/ │ │ └── env.ts │ └── tsconfig.json ├── package.json ├── packages/ │ ├── @core/ │ │ ├── README.md │ │ ├── base/ │ │ │ ├── README.md │ │ │ ├── design/ │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── css/ │ │ │ │ │ │ ├── global.css │ │ │ │ │ │ ├── nprogress.css │ │ │ │ │ │ ├── transition.css │ │ │ │ │ │ └── ui.css │ │ │ │ │ ├── design-tokens/ │ │ │ │ │ │ ├── dark.css │ │ │ │ │ │ ├── default.css │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── scss-bem/ │ │ │ │ │ ├── bem.scss │ │ │ │ │ └── constants.scss │ │ │ │ ├── tsconfig.json │ │ │ │ └── vite.config.mts │ │ │ ├── icons/ │ │ │ │ ├── build.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── create-icon.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── lucide.ts │ │ │ │ └── tsconfig.json │ │ │ ├── shared/ │ │ │ │ ├── build.config.ts │ │ │ │ ├── package.json │ │ │ │ ├── src/ │ │ │ │ │ ├── cache/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ └── storage-manager.test.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── storage-manager.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── color/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ └── convert.test.ts │ │ │ │ │ │ ├── color.ts │ │ │ │ │ │ ├── convert.ts │ │ │ │ │ │ ├── generator.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── constants/ │ │ │ │ │ │ ├── dict-enum.ts │ │ │ │ │ │ ├── globals.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── vben.ts │ │ │ │ │ ├── global-state.ts │ │ │ │ │ ├── store.ts │ │ │ │ │ └── utils/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ ├── diff.test.ts │ │ │ │ │ │ ├── dom.test.ts │ │ │ │ │ │ ├── inference.test.ts │ │ │ │ │ │ ├── letter.test.ts │ │ │ │ │ │ ├── resources.test.ts │ │ │ │ │ │ ├── state-handler.test.ts │ │ │ │ │ │ ├── tree.test.ts │ │ │ │ │ │ ├── unique.test.ts │ │ │ │ │ │ ├── update-css-variables.test.ts │ │ │ │ │ │ ├── util.test.ts │ │ │ │ │ │ └── window.test.ts │ │ │ │ │ ├── cn.ts │ │ │ │ │ ├── date.ts │ │ │ │ │ ├── diff.ts │ │ │ │ │ ├── dom.ts │ │ │ │ │ ├── download.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── inference.ts │ │ │ │ │ ├── letter.ts │ │ │ │ │ ├── merge.ts │ │ │ │ │ ├── nprogress.ts │ │ │ │ │ ├── resources.ts │ │ │ │ │ ├── state-handler.ts │ │ │ │ │ ├── to.ts │ │ │ │ │ ├── tree.ts │ │ │ │ │ ├── unique.ts │ │ │ │ │ ├── update-css-variables.ts │ │ │ │ │ ├── util.ts │ │ │ │ │ └── window.ts │ │ │ │ └── tsconfig.json │ │ │ └── typings/ │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── app.d.ts │ │ │ │ ├── basic.d.ts │ │ │ │ ├── helper.d.ts │ │ │ │ ├── index.ts │ │ │ │ ├── menu-record.ts │ │ │ │ ├── tabs.ts │ │ │ │ └── vue-router.d.ts │ │ │ ├── tsconfig.json │ │ │ └── vue-router.d.ts │ │ ├── composables/ │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── use-sortable.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── use-is-mobile.ts │ │ │ │ ├── use-layout-style.ts │ │ │ │ ├── use-namespace.ts │ │ │ │ ├── use-priority-value.ts │ │ │ │ ├── use-scroll-lock.ts │ │ │ │ ├── use-simple-locale/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── index.ts │ │ │ │ │ └── messages.ts │ │ │ │ └── use-sortable.ts │ │ │ └── tsconfig.json │ │ ├── preferences/ │ │ │ ├── __tests__/ │ │ │ │ ├── __snapshots__/ │ │ │ │ │ └── config.test.ts.snap │ │ │ │ ├── config.test.ts │ │ │ │ └── preferences.test.ts │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── config.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── preferences.ts │ │ │ │ ├── types.ts │ │ │ │ ├── update-css-variables.ts │ │ │ │ └── use-preferences.ts │ │ │ └── tsconfig.json │ │ └── ui-kit/ │ │ ├── README.md │ │ ├── form-ui/ │ │ │ ├── __tests__/ │ │ │ │ └── form-api.test.ts │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── postcss.config.mjs │ │ │ ├── src/ │ │ │ │ ├── components/ │ │ │ │ │ └── form-actions.vue │ │ │ │ ├── config.ts │ │ │ │ ├── form-api.ts │ │ │ │ ├── form-render/ │ │ │ │ │ ├── context.ts │ │ │ │ │ ├── dependencies.ts │ │ │ │ │ ├── expandable.ts │ │ │ │ │ ├── form-field.vue │ │ │ │ │ ├── form-label.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── helper.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ ├── use-form-context.ts │ │ │ │ ├── use-vben-form.ts │ │ │ │ ├── vben-form.vue │ │ │ │ └── vben-use-form.vue │ │ │ ├── tailwind.config.mjs │ │ │ └── tsconfig.json │ │ ├── layout-ui/ │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── postcss.config.mjs │ │ │ ├── src/ │ │ │ │ ├── components/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layout-content.vue │ │ │ │ │ ├── layout-footer.vue │ │ │ │ │ ├── layout-header.vue │ │ │ │ │ ├── layout-sidebar.vue │ │ │ │ │ ├── layout-tabbar.vue │ │ │ │ │ └── widgets/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── sidebar-collapse-button.vue │ │ │ │ │ └── sidebar-fixed-button.vue │ │ │ │ ├── hooks/ │ │ │ │ │ └── use-layout.ts │ │ │ │ ├── index.ts │ │ │ │ ├── vben-layout.ts │ │ │ │ └── vben-layout.vue │ │ │ ├── tailwind.config.mjs │ │ │ └── tsconfig.json │ │ ├── menu-ui/ │ │ │ ├── README.md │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── postcss.config.mjs │ │ │ ├── src/ │ │ │ │ ├── components/ │ │ │ │ │ ├── collapse-transition.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── menu-badge-dot.vue │ │ │ │ │ ├── menu-badge.vue │ │ │ │ │ ├── menu-item.vue │ │ │ │ │ ├── menu.vue │ │ │ │ │ ├── normal-menu/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── normal-menu.ts │ │ │ │ │ │ └── normal-menu.vue │ │ │ │ │ ├── sub-menu-content.vue │ │ │ │ │ └── sub-menu.vue │ │ │ │ ├── hooks/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── use-menu-context.ts │ │ │ │ │ ├── use-menu-scroll.ts │ │ │ │ │ └── use-menu.ts │ │ │ │ ├── index.ts │ │ │ │ ├── menu.vue │ │ │ │ ├── sub-menu.vue │ │ │ │ ├── types.ts │ │ │ │ └── utils/ │ │ │ │ └── index.ts │ │ │ ├── tailwind.config.mjs │ │ │ └── tsconfig.json │ │ ├── popup-ui/ │ │ │ ├── build.config.ts │ │ │ ├── package.json │ │ │ ├── postcss.config.mjs │ │ │ ├── src/ │ │ │ │ ├── alert/ │ │ │ │ │ ├── AlertBuilder.ts │ │ │ │ │ ├── alert.ts │ │ │ │ │ ├── alert.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── drawer/ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ └── drawer-api.test.ts │ │ │ │ │ ├── drawer-api.ts │ │ │ │ │ ├── drawer.ts │ │ │ │ │ ├── drawer.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── use-drawer.ts │ │ │ │ ├── index.ts │ │ │ │ └── modal/ │ │ │ │ ├── __tests__/ │ │ │ │ │ └── modal-api.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── modal-api.ts │ │ │ │ ├── modal.ts │ │ │ │ ├── modal.vue │ │ │ │ ├── use-modal-draggable.ts │ │ │ │ └── use-modal.ts │ │ │ ├── tailwind.config.mjs │ │ │ └── tsconfig.json │ │ ├── shadcn-ui/ │ │ │ ├── build.config.ts │ │ │ ├── components.json │ │ │ ├── package.json │ │ │ ├── postcss.config.mjs │ │ │ ├── src/ │ │ │ │ ├── components/ │ │ │ │ │ ├── avatar/ │ │ │ │ │ │ ├── avatar.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── back-top/ │ │ │ │ │ │ ├── back-top.vue │ │ │ │ │ │ ├── backtop.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── use-backtop.ts │ │ │ │ │ ├── breadcrumb/ │ │ │ │ │ │ ├── breadcrumb-background.vue │ │ │ │ │ │ ├── breadcrumb-view.vue │ │ │ │ │ │ ├── breadcrumb.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── button/ │ │ │ │ │ │ ├── button-group.vue │ │ │ │ │ │ ├── button.ts │ │ │ │ │ │ ├── button.vue │ │ │ │ │ │ ├── check-button-group.vue │ │ │ │ │ │ ├── icon-button.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── checkbox/ │ │ │ │ │ │ ├── checkbox.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── context-menu/ │ │ │ │ │ │ ├── context-menu.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── interface.ts │ │ │ │ │ ├── count-to-animator/ │ │ │ │ │ │ ├── count-to-animator.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── dropdown-menu/ │ │ │ │ │ │ ├── dropdown-menu.vue │ │ │ │ │ │ ├── dropdown-radio-menu.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── interface.ts │ │ │ │ │ ├── expandable-arrow/ │ │ │ │ │ │ ├── expandable-arrow.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── full-screen/ │ │ │ │ │ │ ├── full-screen.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── hover-card/ │ │ │ │ │ │ ├── hover-card.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── icon/ │ │ │ │ │ │ ├── icon.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── input-captcha/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── input-captcha.vue │ │ │ │ │ ├── input-password/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── input-password.vue │ │ │ │ │ │ └── password-strength.vue │ │ │ │ │ ├── logo/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── logo.vue │ │ │ │ │ ├── pin-input/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── input.vue │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── popover/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── popover.vue │ │ │ │ │ ├── render-content/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── render-content.vue │ │ │ │ │ ├── scrollbar/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── scrollbar.vue │ │ │ │ │ ├── segmented/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── segmented.vue │ │ │ │ │ │ ├── tabs-indicator.vue │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── select/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── select.vue │ │ │ │ │ ├── spine-text/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── spine-text.vue │ │ │ │ │ ├── spinner/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── loading.vue │ │ │ │ │ │ └── spinner.vue │ │ │ │ │ └── tooltip/ │ │ │ │ │ ├── help-tooltip.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── tooltip.vue │ │ │ │ ├── index.ts │ │ │ │ └── ui/ │ │ │ │ ├── accordion/ │ │ │ │ │ ├── Accordion.vue │ │ │ │ │ ├── AccordionContent.vue │ │ │ │ │ ├── AccordionItem.vue │ │ │ │ │ ├── AccordionTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── alert-dialog/ │ │ │ │ │ ├── AlertDialog.vue │ │ │ │ │ ├── AlertDialogAction.vue │ │ │ │ │ ├── AlertDialogCancel.vue │ │ │ │ │ ├── AlertDialogContent.vue │ │ │ │ │ ├── AlertDialogDescription.vue │ │ │ │ │ ├── AlertDialogOverlay.vue │ │ │ │ │ ├── AlertDialogTitle.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── avatar/ │ │ │ │ │ ├── Avatar.vue │ │ │ │ │ ├── AvatarFallback.vue │ │ │ │ │ ├── AvatarImage.vue │ │ │ │ │ ├── avatar.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── badge/ │ │ │ │ │ ├── Badge.vue │ │ │ │ │ ├── badge.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── breadcrumb/ │ │ │ │ │ ├── Breadcrumb.vue │ │ │ │ │ ├── BreadcrumbEllipsis.vue │ │ │ │ │ ├── BreadcrumbItem.vue │ │ │ │ │ ├── BreadcrumbLink.vue │ │ │ │ │ ├── BreadcrumbList.vue │ │ │ │ │ ├── BreadcrumbPage.vue │ │ │ │ │ ├── BreadcrumbSeparator.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── button/ │ │ │ │ │ ├── Button.vue │ │ │ │ │ ├── button.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ ├── card/ │ │ │ │ │ ├── Card.vue │ │ │ │ │ ├── CardContent.vue │ │ │ │ │ ├── CardDescription.vue │ │ │ │ │ ├── CardFooter.vue │ │ │ │ │ ├── CardHeader.vue │ │ │ │ │ ├── CardTitle.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── checkbox/ │ │ │ │ │ ├── Checkbox.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── context-menu/ │ │ │ │ │ ├── ContextMenu.vue │ │ │ │ │ ├── ContextMenuCheckboxItem.vue │ │ │ │ │ ├── ContextMenuContent.vue │ │ │ │ │ ├── ContextMenuGroup.vue │ │ │ │ │ ├── ContextMenuItem.vue │ │ │ │ │ ├── ContextMenuLabel.vue │ │ │ │ │ ├── ContextMenuPortal.vue │ │ │ │ │ ├── ContextMenuRadioGroup.vue │ │ │ │ │ ├── ContextMenuRadioItem.vue │ │ │ │ │ ├── ContextMenuSeparator.vue │ │ │ │ │ ├── ContextMenuShortcut.vue │ │ │ │ │ ├── ContextMenuSub.vue │ │ │ │ │ ├── ContextMenuSubContent.vue │ │ │ │ │ ├── ContextMenuSubTrigger.vue │ │ │ │ │ ├── ContextMenuTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── dialog/ │ │ │ │ │ ├── Dialog.vue │ │ │ │ │ ├── DialogClose.vue │ │ │ │ │ ├── DialogContent.vue │ │ │ │ │ ├── DialogDescription.vue │ │ │ │ │ ├── DialogFooter.vue │ │ │ │ │ ├── DialogHeader.vue │ │ │ │ │ ├── DialogOverlay.vue │ │ │ │ │ ├── DialogScrollContent.vue │ │ │ │ │ ├── DialogTitle.vue │ │ │ │ │ ├── DialogTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── dropdown-menu/ │ │ │ │ │ ├── DropdownMenu.vue │ │ │ │ │ ├── DropdownMenuCheckboxItem.vue │ │ │ │ │ ├── DropdownMenuContent.vue │ │ │ │ │ ├── DropdownMenuGroup.vue │ │ │ │ │ ├── DropdownMenuItem.vue │ │ │ │ │ ├── DropdownMenuLabel.vue │ │ │ │ │ ├── DropdownMenuRadioGroup.vue │ │ │ │ │ ├── DropdownMenuRadioItem.vue │ │ │ │ │ ├── DropdownMenuSeparator.vue │ │ │ │ │ ├── DropdownMenuShortcut.vue │ │ │ │ │ ├── DropdownMenuSub.vue │ │ │ │ │ ├── DropdownMenuSubContent.vue │ │ │ │ │ ├── DropdownMenuSubTrigger.vue │ │ │ │ │ ├── DropdownMenuTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── form/ │ │ │ │ │ ├── FormControl.vue │ │ │ │ │ ├── FormDescription.vue │ │ │ │ │ ├── FormItem.vue │ │ │ │ │ ├── FormLabel.vue │ │ │ │ │ ├── FormMessage.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── injectionKeys.ts │ │ │ │ │ └── useFormField.ts │ │ │ │ ├── hover-card/ │ │ │ │ │ ├── HoverCard.vue │ │ │ │ │ ├── HoverCardContent.vue │ │ │ │ │ ├── HoverCardTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── input/ │ │ │ │ │ ├── Input.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── label/ │ │ │ │ │ ├── Label.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── number-field/ │ │ │ │ │ ├── NumberField.vue │ │ │ │ │ ├── NumberFieldContent.vue │ │ │ │ │ ├── NumberFieldDecrement.vue │ │ │ │ │ ├── NumberFieldIncrement.vue │ │ │ │ │ ├── NumberFieldInput.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── pagination/ │ │ │ │ │ ├── PaginationEllipsis.vue │ │ │ │ │ ├── PaginationFirst.vue │ │ │ │ │ ├── PaginationLast.vue │ │ │ │ │ ├── PaginationNext.vue │ │ │ │ │ ├── PaginationPrev.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── pin-input/ │ │ │ │ │ ├── PinInput.vue │ │ │ │ │ ├── PinInputGroup.vue │ │ │ │ │ ├── PinInputInput.vue │ │ │ │ │ ├── PinInputSeparator.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── popover/ │ │ │ │ │ ├── Popover.vue │ │ │ │ │ ├── PopoverContent.vue │ │ │ │ │ ├── PopoverTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── radio-group/ │ │ │ │ │ ├── RadioGroup.vue │ │ │ │ │ ├── RadioGroupItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── resizable/ │ │ │ │ │ ├── ResizableHandle.vue │ │ │ │ │ ├── ResizablePanelGroup.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── scroll-area/ │ │ │ │ │ ├── ScrollArea.vue │ │ │ │ │ ├── ScrollBar.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── select/ │ │ │ │ │ ├── Select.vue │ │ │ │ │ ├── SelectContent.vue │ │ │ │ │ ├── SelectGroup.vue │ │ │ │ │ ├── SelectItem.vue │ │ │ │ │ ├── SelectItemText.vue │ │ │ │ │ ├── SelectLabel.vue │ │ │ │ │ ├── SelectScrollDownButton.vue │ │ │ │ │ ├── SelectScrollUpButton.vue │ │ │ │ │ ├── SelectSeparator.vue │ │ │ │ │ ├── SelectTrigger.vue │ │ │ │ │ ├── SelectValue.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── separator/ │ │ │ │ │ ├── Separator.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── sheet/ │ │ │ │ │ ├── Sheet.vue │ │ │ │ │ ├── SheetClose.vue │ │ │ │ │ ├── SheetContent.vue │ │ │ │ │ ├── SheetDescription.vue │ │ │ │ │ ├── SheetFooter.vue │ │ │ │ │ ├── SheetHeader.vue │ │ │ │ │ ├── SheetOverlay.vue │ │ │ │ │ ├── SheetTitle.vue │ │ │ │ │ ├── SheetTrigger.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── sheet.ts │ │ │ │ ├── switch/ │ │ │ │ │ ├── Switch.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── tabs/ │ │ │ │ │ ├── Tabs.vue │ │ │ │ │ ├── TabsContent.vue │ │ │ │ │ ├── TabsList.vue │ │ │ │ │ ├── TabsTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── textarea/ │ │ │ │ │ ├── Textarea.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── toggle/ │ │ │ │ │ ├── Toggle.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── toggle.ts │ │ │ │ ├── toggle-group/ │ │ │ │ │ ├── ToggleGroup.vue │ │ │ │ │ ├── ToggleGroupItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── tooltip/ │ │ │ │ │ ├── Tooltip.vue │ │ │ │ │ ├── TooltipContent.vue │ │ │ │ │ ├── TooltipProvider.vue │ │ │ │ │ ├── TooltipTrigger.vue │ │ │ │ │ └── index.ts │ │ │ │ └── tree/ │ │ │ │ ├── index.ts │ │ │ │ ├── tree.vue │ │ │ │ └── types.ts │ │ │ ├── tailwind.config.mjs │ │ │ └── tsconfig.json │ │ └── tabs-ui/ │ │ ├── build.config.ts │ │ ├── package.json │ │ ├── postcss.config.mjs │ │ ├── src/ │ │ │ ├── components/ │ │ │ │ ├── index.ts │ │ │ │ ├── tabs/ │ │ │ │ │ └── tabs.vue │ │ │ │ ├── tabs-chrome/ │ │ │ │ │ └── tabs.vue │ │ │ │ └── widgets/ │ │ │ │ ├── index.ts │ │ │ │ ├── tool-more.vue │ │ │ │ └── tool-screen.vue │ │ │ ├── index.ts │ │ │ ├── tabs-view.vue │ │ │ ├── types.ts │ │ │ ├── use-tabs-drag.ts │ │ │ └── use-tabs-view-scroll.ts │ │ ├── tailwind.config.mjs │ │ └── tsconfig.json │ ├── constants/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── core.ts │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── effects/ │ │ ├── README.md │ │ ├── access/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── access-control.vue │ │ │ │ ├── accessible.ts │ │ │ │ ├── directive.ts │ │ │ │ ├── index.ts │ │ │ │ └── use-access.ts │ │ │ └── tsconfig.json │ │ ├── common-ui/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── components/ │ │ │ │ │ ├── api-component/ │ │ │ │ │ │ ├── api-component.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── captcha/ │ │ │ │ │ │ ├── hooks/ │ │ │ │ │ │ │ └── useCaptchaPoints.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── point-selection-captcha/ │ │ │ │ │ │ │ ├── index.vue │ │ │ │ │ │ │ └── point-selection-captcha-card.vue │ │ │ │ │ │ ├── slider-captcha/ │ │ │ │ │ │ │ ├── index.vue │ │ │ │ │ │ │ ├── slider-captcha-action.vue │ │ │ │ │ │ │ ├── slider-captcha-bar.vue │ │ │ │ │ │ │ └── slider-captcha-content.vue │ │ │ │ │ │ ├── slider-rotate-captcha/ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ ├── slider-translate-captcha/ │ │ │ │ │ │ │ └── index.vue │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── code-mirror/ │ │ │ │ │ │ ├── code-mirror.vue │ │ │ │ │ │ ├── data.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── col-page/ │ │ │ │ │ │ ├── col-page.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── count-to/ │ │ │ │ │ │ ├── count-to.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── ellipsis-text/ │ │ │ │ │ │ ├── ellipsis-text.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── icon-picker/ │ │ │ │ │ │ ├── icon-picker.vue │ │ │ │ │ │ ├── icons.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── json-preview/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── json-preview.vue │ │ │ │ │ ├── json-viewer/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── index.vue │ │ │ │ │ │ ├── style.scss │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── loading/ │ │ │ │ │ │ ├── directive.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── loading.vue │ │ │ │ │ │ └── spinner.vue │ │ │ │ │ ├── markdown/ │ │ │ │ │ │ ├── editor.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── preview.vue │ │ │ │ │ ├── page/ │ │ │ │ │ │ ├── __tests__/ │ │ │ │ │ │ │ └── page.test.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── page.vue │ │ │ │ │ │ └── types.ts │ │ │ │ │ ├── resize/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── resize.vue │ │ │ │ │ ├── tippy/ │ │ │ │ │ │ ├── directive.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── tree/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── tree.vue │ │ │ │ ├── index.ts │ │ │ │ └── ui/ │ │ │ │ ├── about/ │ │ │ │ │ ├── about.ts │ │ │ │ │ ├── about.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── authentication/ │ │ │ │ │ ├── auth-title.vue │ │ │ │ │ ├── code-login.vue │ │ │ │ │ ├── dingding-login.vue │ │ │ │ │ ├── forget-password.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── login-expired-modal.vue │ │ │ │ │ ├── login.vue │ │ │ │ │ ├── qrcode-login.vue │ │ │ │ │ ├── register.vue │ │ │ │ │ ├── third-party-login.vue │ │ │ │ │ └── types.ts │ │ │ │ ├── dashboard/ │ │ │ │ │ ├── analysis/ │ │ │ │ │ │ ├── analysis-chart-card.vue │ │ │ │ │ │ ├── analysis-charts-tabs.vue │ │ │ │ │ │ ├── analysis-overview.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── typing.ts │ │ │ │ │ └── workbench/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── workbench-header.vue │ │ │ │ │ ├── workbench-project.vue │ │ │ │ │ ├── workbench-quick-nav.vue │ │ │ │ │ ├── workbench-todo.vue │ │ │ │ │ └── workbench-trends.vue │ │ │ │ ├── fallback/ │ │ │ │ │ ├── fallback.ts │ │ │ │ │ ├── fallback.vue │ │ │ │ │ ├── icons/ │ │ │ │ │ │ ├── icon-403.vue │ │ │ │ │ │ ├── icon-404.vue │ │ │ │ │ │ ├── icon-500.vue │ │ │ │ │ │ ├── icon-coming-soon.vue │ │ │ │ │ │ └── icon-offline.vue │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ ├── hooks/ │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── index.ts │ │ │ │ ├── use-app-config.ts │ │ │ │ ├── use-content-maximize.ts │ │ │ │ ├── use-design-tokens.ts │ │ │ │ ├── use-hover-toggle.ts │ │ │ │ ├── use-pagination.ts │ │ │ │ ├── use-refresh.ts │ │ │ │ ├── use-tabs.ts │ │ │ │ └── use-watermark.ts │ │ │ └── tsconfig.json │ │ ├── layouts/ │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── authentication/ │ │ │ │ │ ├── authentication.vue │ │ │ │ │ ├── form.vue │ │ │ │ │ ├── icons/ │ │ │ │ │ │ └── slogan.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── toolbar.vue │ │ │ │ │ └── types.ts │ │ │ │ ├── basic/ │ │ │ │ │ ├── README.md │ │ │ │ │ ├── content/ │ │ │ │ │ │ ├── content-spinner.vue │ │ │ │ │ │ ├── content.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── use-content-spinner.ts │ │ │ │ │ ├── copyright/ │ │ │ │ │ │ ├── copyright.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── footer/ │ │ │ │ │ │ ├── footer.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── header/ │ │ │ │ │ │ ├── header.vue │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── layout.vue │ │ │ │ │ ├── menu/ │ │ │ │ │ │ ├── extra-menu.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── menu.vue │ │ │ │ │ │ ├── mixed-menu.vue │ │ │ │ │ │ ├── use-extra-menu.ts │ │ │ │ │ │ ├── use-mixed-menu.ts │ │ │ │ │ │ └── use-navigation.ts │ │ │ │ │ └── tabbar/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── tabbar.vue │ │ │ │ │ └── use-tabbar.ts │ │ │ │ ├── iframe/ │ │ │ │ │ ├── iframe-router-view.vue │ │ │ │ │ ├── iframe-view.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── widgets/ │ │ │ │ ├── breadcrumb.vue │ │ │ │ ├── check-updates/ │ │ │ │ │ ├── check-updates.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── color-toggle.vue │ │ │ │ ├── global-search/ │ │ │ │ │ ├── global-search.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── search-panel.vue │ │ │ │ ├── index.ts │ │ │ │ ├── language-toggle.vue │ │ │ │ ├── layout-toggle.vue │ │ │ │ ├── lock-screen/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── lock-screen-modal.vue │ │ │ │ │ └── lock-screen.vue │ │ │ │ ├── notification/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── notification.vue │ │ │ │ │ └── types.ts │ │ │ │ ├── preferences/ │ │ │ │ │ ├── blocks/ │ │ │ │ │ │ ├── block.vue │ │ │ │ │ │ ├── checkbox-item.vue │ │ │ │ │ │ ├── general/ │ │ │ │ │ │ │ ├── animation.vue │ │ │ │ │ │ │ └── general.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── input-item.vue │ │ │ │ │ │ ├── layout/ │ │ │ │ │ │ │ ├── breadcrumb.vue │ │ │ │ │ │ │ ├── content.vue │ │ │ │ │ │ │ ├── copyright.vue │ │ │ │ │ │ │ ├── footer.vue │ │ │ │ │ │ │ ├── header.vue │ │ │ │ │ │ │ ├── layout.vue │ │ │ │ │ │ │ ├── navigation.vue │ │ │ │ │ │ │ ├── sidebar.vue │ │ │ │ │ │ │ ├── tabbar.vue │ │ │ │ │ │ │ └── widget.vue │ │ │ │ │ │ ├── number-field-item.vue │ │ │ │ │ │ ├── select-item.vue │ │ │ │ │ │ ├── shortcut-keys/ │ │ │ │ │ │ │ └── global.vue │ │ │ │ │ │ ├── switch-item.vue │ │ │ │ │ │ ├── theme/ │ │ │ │ │ │ │ ├── builtin.vue │ │ │ │ │ │ │ ├── color-mode.vue │ │ │ │ │ │ │ ├── radius.vue │ │ │ │ │ │ │ └── theme.vue │ │ │ │ │ │ └── toggle-item.vue │ │ │ │ │ ├── icons/ │ │ │ │ │ │ ├── content-compact.vue │ │ │ │ │ │ ├── full-content.vue │ │ │ │ │ │ ├── header-mixed-nav.vue │ │ │ │ │ │ ├── header-nav.vue │ │ │ │ │ │ ├── header-sidebar-nav.vue │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── mixed-nav.vue │ │ │ │ │ │ ├── setting.vue │ │ │ │ │ │ ├── sidebar-mixed-nav.vue │ │ │ │ │ │ └── sidebar-nav.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── preferences-button.vue │ │ │ │ │ ├── preferences-drawer.vue │ │ │ │ │ ├── preferences.vue │ │ │ │ │ └── use-open-preferences.ts │ │ │ │ ├── theme-toggle/ │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── theme-button.vue │ │ │ │ │ └── theme-toggle.vue │ │ │ │ └── user-dropdown/ │ │ │ │ ├── index.ts │ │ │ │ └── user-dropdown.vue │ │ │ └── tsconfig.json │ │ ├── plugins/ │ │ │ ├── README.md │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ ├── echarts/ │ │ │ │ │ ├── echarts-ui.vue │ │ │ │ │ ├── echarts.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── use-echarts.ts │ │ │ │ ├── motion/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── types.ts │ │ │ │ └── vxe-table/ │ │ │ │ ├── api.ts │ │ │ │ ├── extends.ts │ │ │ │ ├── index.ts │ │ │ │ ├── init.ts │ │ │ │ ├── style.css │ │ │ │ ├── types.ts │ │ │ │ ├── use-vxe-grid.ts │ │ │ │ └── use-vxe-grid.vue │ │ │ └── tsconfig.json │ │ └── request/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── request-client/ │ │ │ ├── index.ts │ │ │ ├── modules/ │ │ │ │ ├── downloader.test.ts │ │ │ │ ├── downloader.ts │ │ │ │ ├── interceptor.ts │ │ │ │ ├── sse.test.ts │ │ │ │ ├── sse.ts │ │ │ │ ├── uploader.test.ts │ │ │ │ └── uploader.ts │ │ │ ├── preset-interceptors.ts │ │ │ ├── request-client.test.ts │ │ │ ├── request-client.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── icons/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── iconify/ │ │ │ │ └── index.ts │ │ │ ├── iconify-offline/ │ │ │ │ ├── index.ts │ │ │ │ └── menu-icons.ts │ │ │ ├── icons/ │ │ │ │ └── empty-icon.vue │ │ │ ├── index.ts │ │ │ └── svg/ │ │ │ ├── index.ts │ │ │ └── load.ts │ │ └── tsconfig.json │ ├── locales/ │ │ ├── package.json │ │ ├── src/ │ │ │ ├── i18n.ts │ │ │ ├── index.ts │ │ │ ├── langs/ │ │ │ │ ├── en-US/ │ │ │ │ │ ├── authentication.json │ │ │ │ │ ├── common.json │ │ │ │ │ ├── preferences.json │ │ │ │ │ └── ui.json │ │ │ │ └── zh-CN/ │ │ │ │ ├── authentication.json │ │ │ │ ├── common.json │ │ │ │ ├── preferences.json │ │ │ │ └── ui.json │ │ │ └── typing.ts │ │ └── tsconfig.json │ ├── preferences/ │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ └── tsconfig.json │ ├── stores/ │ │ ├── package.json │ │ ├── shim-pinia.d.ts │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── modules/ │ │ │ │ ├── access.test.ts │ │ │ │ ├── access.ts │ │ │ │ ├── index.ts │ │ │ │ ├── tabbar.test.ts │ │ │ │ ├── tabbar.ts │ │ │ │ ├── user.test.ts │ │ │ │ └── user.ts │ │ │ └── setup.ts │ │ └── tsconfig.json │ ├── styles/ │ │ ├── README.md │ │ ├── package.json │ │ ├── src/ │ │ │ ├── antd/ │ │ │ │ └── index.css │ │ │ ├── ele/ │ │ │ │ └── index.css │ │ │ ├── global/ │ │ │ │ └── index.scss │ │ │ ├── index.ts │ │ │ └── naive/ │ │ │ └── index.css │ │ └── tsconfig.json │ ├── types/ │ │ ├── README.md │ │ ├── global.d.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── user.ts │ │ └── tsconfig.json │ └── utils/ │ ├── README.md │ ├── package.json │ ├── src/ │ │ ├── encryption/ │ │ │ ├── base.ts │ │ │ ├── crypto.ts │ │ │ ├── impl/ │ │ │ │ ├── aes.ts │ │ │ │ ├── rsa.ts │ │ │ │ ├── sm2.ts │ │ │ │ └── sm4.ts │ │ │ └── index.ts │ │ ├── helpers/ │ │ │ ├── __tests__/ │ │ │ │ ├── enum-options.test.ts │ │ │ │ ├── find-menu-by-path.test.ts │ │ │ │ ├── generate-menus.test.ts │ │ │ │ ├── generate-routes-frontend.test.ts │ │ │ │ └── merge-route-modules.test.ts │ │ │ ├── enum-options.ts │ │ │ ├── find-menu-by-path.ts │ │ │ ├── generate-menus.ts │ │ │ ├── generate-routes-backend.ts │ │ │ ├── generate-routes-frontend.ts │ │ │ ├── get-popup-container.ts │ │ │ ├── index.ts │ │ │ ├── merge-route-modules.ts │ │ │ ├── mitt.ts │ │ │ ├── request.ts │ │ │ ├── reset-routes.ts │ │ │ ├── safe.ts │ │ │ ├── tree.ts │ │ │ ├── unmount-global-loading.ts │ │ │ └── uuid.ts │ │ └── index.ts │ └── tsconfig.json ├── pnpm-workspace.yaml ├── scripts/ │ ├── clean.mjs │ ├── deploy/ │ │ ├── Dockerfile │ │ ├── build-local-docker-image.sh │ │ └── nginx.conf │ ├── turbo-run/ │ │ ├── README.md │ │ ├── bin/ │ │ │ └── turbo-run.mjs │ │ ├── build.config.ts │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── run.ts │ │ └── tsconfig.json │ └── vsh/ │ ├── README.md │ ├── bin/ │ │ └── vsh.mjs │ ├── build.config.ts │ ├── package.json │ ├── src/ │ │ ├── check-circular/ │ │ │ └── index.ts │ │ ├── check-dep/ │ │ │ └── index.ts │ │ ├── code-workspace/ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── lint/ │ │ │ └── index.ts │ │ └── publint/ │ │ └── index.ts │ └── tsconfig.json ├── stylelint.config.mjs └── turbo.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .browserslistrc ================================================ > 1% last 2 versions not dead not ie 11 ================================================ FILE: .commitlintrc.js ================================================ export { default } from '@vben/commitlint-config'; ================================================ FILE: .dockerignore ================================================ # Git .git .gitignore # IDE .idea .vscode *.iml # Logs *.log logs # Node node_modules .pnpm-store .turbo # Build outputs (will be rebuilt in container) apps/*/dist apps/*/.turbo packages/*/dist packages/*/.turbo # Test coverage *.test.* *.spec.* # Docs *.md !README.md docs # Misc .DS_Store .env .env.local .env.*.local ================================================ FILE: .editorconfig ================================================ root = true [*] charset=utf-8 end_of_line=lf insert_final_newline=true indent_style=space indent_size=2 max_line_length = 100 trim_trailing_whitespace = true quote_type = single [*.{yml,yaml,json}] indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false ================================================ FILE: .gitattributes ================================================ # https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings # Automatically normalize line endings (to LF) for all text-based files. * text=auto eol=lf # Declare files that will always have CRLF line endings on checkout. *.{cmd,[cC][mM][dD]} text eol=crlf *.{bat,[bB][aA][tT]} text eol=crlf # Denote all files that are truly binary and should not be modified. *.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary ================================================ FILE: .gitconfig ================================================ [core] ignorecase = false ================================================ FILE: .gitignore ================================================ node_modules .DS_Store dist dist-ssr dist.zip dist.tar dist.war .nitro .output *-dist.zip *-dist.tar *-dist.war coverage *.local **/.vitepress/cache .cache .turbo .temp dev-dist .stylelintcache yarn.lock package-lock.json pnpm-lock.yaml .VSCodeCounter **/backend-mock/data # local env files .env.local .env.*.local .eslintcache logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* vite.config.mts.* vite.config.mjs.* vite.config.js.* vite.config.ts.* # Editor directories and files .idea .vscode *.suo *.ntvs* *.njsproj *.sln *.sw? # 排除自动生成的类型文件 apps/web-antd/types/components.d.ts .history .cursor .claude ================================================ FILE: .npmrc ================================================ registry = "https://registry.npmmirror.com" public-hoist-pattern[]=lefthook public-hoist-pattern[]=eslint public-hoist-pattern[]=prettier public-hoist-pattern[]=prettier-plugin-tailwindcss public-hoist-pattern[]=stylelint public-hoist-pattern[]=*postcss* public-hoist-pattern[]=@commitlint/* public-hoist-pattern[]=czg strict-peer-dependencies=false auto-install-peers=true dedupe-peer-dependents=true ================================================ FILE: .prettierignore ================================================ dist dev-dist .local .output.js node_modules .nvmrc coverage CODEOWNERS .nitro .output **/*.svg **/*.sh public .npmrc *-lock.yaml ================================================ FILE: .prettierrc.mjs ================================================ // .prettierrc.mjs 是 Prettier 代码格式化工具的配置文件。 // 作用: // 统一代码风格(缩进、引号、分号等) // 在保存文件或提交代码时自动格式化 export { default } from '@vben/prettier-config'; ================================================ FILE: .stylelintignore ================================================ dist public __tests__ coverage ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2026 ruoyi-ai Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # RuoYi-AI 管理端
RuoYi AI Logo ### 企业级AI助手平台 - 管理后台 *RuoYi-AI 的管理后台,提供系统管理、模型配置、知识库管理、流程编排等功能* **[在线体验](https://admin.pandarobot.chat)** | **[后端服务](https://github.com/ageerle/ruoyi-ai)** | **[用户端](https://github.com/ageerle/ruoyi-web)**
## 技术栈 - **框架**: Vue 3 + Vben Admin - **UI组件**: element-plus-x - **构建工具**: Vite ## Docker 部署 本管理端支持两种 Docker 部署方式: ### 方式一:一键启动所有服务(推荐) 使用 `docker-compose-all.yaml` 可以一键启动所有服务(包括后端、管理端、用户端及依赖服务): ```bash # 克隆后端仓库 git clone https://github.com/ageerle/ruoyi-ai.git cd ruoyi-ai # 启动所有服务(从镜像仓库拉取预构建镜像) docker-compose -f docker-compose-all.yaml up -d # 访问管理端 # 地址: http://localhost:25666 # 账号: admin / admin123 ``` ### 方式二:分步部署(源码编译) 如果您需要从源码构建,请按照以下步骤操作: #### 第一步:部署后端服务 ```bash # 进入后端项目目录 cd ruoyi-ai # 启动后端服务(源码编译构建) docker-compose up -d --build # 等待后端服务启动完成 docker-compose logs -f backend ``` #### 第二步:部署管理端 ```bash # 进入管理端项目目录 cd ruoyi-admin # 构建并启动管理端 docker-compose up -d --build # 访问管理端 # 地址: http://localhost:5666 ``` #### 第三步:部署用户端(可选) ```bash # 进入用户端项目目录 cd ruoyi-web # 构建并启动用户端 docker-compose up -d --build # 访问用户端 # 地址: http://localhost:5137 ``` ### 服务端口说明 | 服务 | 端口 | 说明 | |------|------|------| | 管理端 | 5666 | 管理后台访问地址 | | 用户端 | 5137 | 用户前端访问地址 | | 后端服务 | 6039 | 后端 API 服务 | | MySQL | 23306 | 数据库服务 | | Redis | 6379 | 缓存服务 | | Weaviate | 28080 | 向量数据库 | | MinIO | 9000/9090 | 对象存储 | ### 镜像仓库 所有镜像托管在阿里云容器镜像服务: ``` crpi-31mraxd99y2gqdgr.cn-beijing.personal.cr.aliyuncs.com/ruoyi_ai ``` 可用镜像: - `mysql:v3` - MySQL 数据库(包含初始化 SQL) - `redis:6.2` - Redis 缓存 - `weaviate:1.30.0` - 向量数据库 - `minio:latest` - 对象存储 - `ruoyi-ai-backend:latest` - 后端服务 - `ruoyi-ai-admin:latest` - 管理端前端 - `ruoyi-ai-web:latest` - 用户端前端 ## 本地开发 ```bash # 安装依赖 pnpm install # 启动开发服务器 pnpm dev # 构建生产版本 pnpm build ``` ## 常见问题 **Q: 管理端无法连接后端服务?** A: 请确保后端服务已启动,并检查环境变量 `UPSTREAM_HOST` 配置是否正确。 **Q: 一键启动和分步部署有什么区别?** A: 一键启动使用预构建的镜像,部署速度快;分步部署从源码编译,适合需要自定义修改的场景。 ## 开源协议 本项目采用 **MIT 开源协议**,详情请查看 [LICENSE](license) 文件。 ---
**[⭐ 点个Star支持一下](https://github.com/ageerle/ruoyi-admin)** • **[Fork 开始贡献](https://github.com/ageerle/ruoyi-admin/fork)** *用 ❤️ 打造,由 RuoYi AI 开源社区维护*
================================================ FILE: apps/web-antd/Dockerfile ================================================ # 构建阶段 FROM node:22-alpine AS builder # 接收后端 API 地址(docker-compose 传入) ARG VITE_GLOB_API_URL=http://localhost:6039 ENV VITE_GLOB_API_URL=${VITE_GLOB_API_URL} # 设置环境变量 - 优化资源使用,增加内存限制 ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" ENV NODE_OPTIONS=--max-old-space-size=16384 # 安装 pnpm 和设置时区 RUN npm i -g corepack && corepack enable && \ apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone # 设置工作目录 WORKDIR /app # 先复制 package 文件(优化 Docker 缓存) COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json .npmrc ./ # 复制 internal 目录(包含 @vben/vite-config, @vben/tsconfig 等) COPY internal ./internal # 复制 scripts 目录(turbo 依赖) COPY scripts ./scripts # 复制 packages 目录(workspace 依赖) COPY packages ./packages # 复制 web-antd 应用 COPY apps/web-antd ./apps/web-antd # 安装依赖(使用 workspace) RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile # 构建 workspace 依赖包(使用 turbo) RUN pnpm run build --filter="./packages/**" # 构建 web-antd 应用(直接构建,避免 turbo 过滤器问题) WORKDIR /app/apps/web-antd RUN pnpm run build:prod WORKDIR /app # 生产阶段 - 使用 nginx 部署 FROM nginx:stable-alpine AS production # 安装 tzdata 并设置时区 RUN apk add --no-cache tzdata && \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ echo "Asia/Shanghai" > /etc/timezone # 配置 nginx 支持 mjs RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf \ && rm -rf /etc/nginx/conf.d/default.conf # 从构建阶段复制构建产物 COPY --from=builder /app/apps/web-antd/dist /usr/share/nginx/html # 复制 nginx 配置文件模板 COPY apps/web-antd/nginx.conf /etc/nginx/nginx.conf.template # 复制启动脚本 COPY apps/web-antd/docker-entrypoint.sh /docker-entrypoint.sh RUN chmod +x /docker-entrypoint.sh # 暴露端口 EXPOSE 5666 # 设置默认后端地址(可被 docker-compose environment 覆盖) ENV UPSTREAM_HOST=127.0.0.1:6039 # 启动脚本(处理环境变量并启动 nginx) CMD ["/docker-entrypoint.sh"] ================================================ FILE: apps/web-antd/docker-compose.yml ================================================ services: # ==================== RuoYi-AI 前端服务 ==================== frontend: image: ruoyi-ai-admin:latest build: context: ../.. dockerfile: apps/web-antd/Dockerfile tags: - ruoyi-ai-admin:latest container_name: ruoyi-ai-admin restart: always ports: - "5666:5666" environment: # 后端 API 地址 - 运行时动态配置(无需重新构建镜像) # 使用后端容器名和内部端口(容器内端口是 6039) UPSTREAM_HOST: ${UPSTREAM_HOST:-ruoyi-ai-backend:6039} # 资源限制 - 防止 CPU 和内存耗尽 deploy: resources: limits: cpus: '2' # 限制最多使用 2 个 CPU 核心 memory: 3G # 限制内存使用为 3GB reservations: cpus: '1' # 保留 1 个 CPU 核心 memory: 1G # 保留 1GB 内存 networks: - ruoyi-net # ==================== 网络配置 ==================== networks: # 使用后端服务的网络,实现容器间通信 ruoyi-net: name: ruoyi-ai_ruoyi-net external: true ================================================ FILE: apps/web-antd/docker-entrypoint.sh ================================================ #!/bin/sh # Nginx 启动脚本 - 支持运行时环境变量 # 使用 envsubst 替换 nginx.conf 中的环境变量 envsubst '${UPSTREAM_HOST}' < /etc/nginx/nginx.conf.template > /etc/nginx/nginx.conf # 启动 nginx exec nginx -g 'daemon off;' ================================================ FILE: apps/web-antd/index.html ================================================ <%= VITE_APP_TITLE %>
================================================ FILE: apps/web-antd/nginx.conf ================================================ worker_processes 1; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; # Gzip 压缩 gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/json application/xml+rss application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml; # 后端 API 地址(运行时环境变量) upstream backend { server ${UPSTREAM_HOST}; } server { listen 5666; server_name localhost; # API 代理到后端服务(去除 /api 前缀后转发) location /api/ { rewrite ^/api/(.*)$ /$1 break; proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 支持WebSocket proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; index index.html; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } } ================================================ FILE: apps/web-antd/package.json ================================================ { "name": "@vben/web-antd", "version": "1.5.2", "homepage": "https://vben.pro", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "apps/web-antd" }, "license": "MIT", "author": { "name": "vben", "email": "ann.vben@gmail.com", "url": "https://github.com/anncwb" }, "type": "module", "scripts": { "build:prod": "pnpm vite build", "build:test": "pnpm vite build --mode test", "build:analyze": "pnpm vite build --mode analyze", "dev": "pnpm vite --mode development", "preview": "vite preview", "typecheck": "vue-tsc --noEmit --skipLibCheck" }, "imports": { "#/*": "./src/*" }, "dependencies": { "@ant-design/icons-vue": "^7.0.1", "@antv/g6": "^5.0.51", "@tinymce/tinymce-vue": "^6.0.1", "@vben/access": "workspace:*", "@vben/common-ui": "workspace:*", "@vben/constants": "workspace:*", "@vben/hooks": "workspace:*", "@vben/icons": "workspace:*", "@vben/layouts": "workspace:*", "@vben/locales": "workspace:*", "@vben/plugins": "workspace:*", "@vben/preferences": "workspace:*", "@vben/request": "workspace:*", "@vben/stores": "workspace:*", "@vben/styles": "workspace:*", "@vben/types": "workspace:*", "@vben/utils": "workspace:*", "@vue-flow/background": "^1.3.2", "@vue-flow/core": "^1.46.5", "@vueuse/core": "catalog:", "ant-design-vue": "catalog:", "cropperjs": "^1.6.2", "dayjs": "catalog:", "echarts": "^5.5.1", "lodash-es": "^4.17.21", "pinia": "catalog:", "tinymce": "7.9.1", "unplugin-vue-components": "^0.27.3", "uuid": "^9.0.0", "vue": "catalog:", "vue-router": "catalog:", "vue3-colorpicker": "^2.3.0" }, "devDependencies": { "@types/lodash-es": "^4.17.12" } } ================================================ FILE: apps/web-antd/postcss.config.mjs ================================================ export { default } from '@vben/tailwind-config/postcss'; ================================================ FILE: apps/web-antd/public/tinymce/langs/README.md ================================================ This is where language files should be placed. Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce ================================================ FILE: apps/web-antd/public/tinymce/langs/zh_CN.js ================================================ tinymce.addI18n("zh_CN",{"#":"#","Accessibility":"\u8f85\u52a9\u529f\u80fd","Accordion":"","Accordion body...":"","Accordion summary...":"","Action":"\u52a8\u4f5c","Activity":"\u6d3b\u52a8","Address":"\u5730\u5740","Advanced":"\u9ad8\u7ea7","Align":"\u5bf9\u9f50","Align center":"\u5c45\u4e2d\u5bf9\u9f50","Align left":"\u5de6\u5bf9\u9f50","Align right":"\u53f3\u5bf9\u9f50","Alignment":"\u5bf9\u9f50","Alignment {0}":"","All":"\u5168\u90e8","Alternative description":"\u66ff\u4ee3\u63cf\u8ff0","Alternative source":"\u955c\u50cf","Alternative source URL":"\u66ff\u4ee3\u6765\u6e90\u7f51\u5740","Anchor":"\u951a\u70b9","Anchor...":"\u951a\u70b9...","Anchors":"\u951a\u70b9","Animals and Nature":"\u52a8\u7269\u548c\u81ea\u7136","Arrows":"\u7bad\u5934","B":"B","Background color":"\u80cc\u666f\u989c\u8272","Background color {0}":"","Black":"\u9ed1\u8272","Block":"\u5757","Block {0}":"","Blockquote":"\u5f15\u6587\u533a\u5757","Blocks":"\u6837\u5f0f","Blue":"\u84dd\u8272","Blue component":"\u767d\u8272\u90e8\u5206","Body":"\u8868\u4f53","Bold":"\u7c97\u4f53","Border":"\u6846\u7ebf","Border color":"\u6846\u7ebf\u989c\u8272","Border style":"\u8fb9\u6846\u6837\u5f0f","Border width":"\u8fb9\u6846\u5bbd\u5ea6","Bottom":"\u4e0b\u65b9\u5bf9\u9f50","Browse files":"","Browse for an image":"\u6d4f\u89c8\u56fe\u50cf","Browse links":"","Bullet list":"\u65e0\u5e8f\u5217\u8868","Cancel":"\u53d6\u6d88","Caption":"\u6807\u9898","Cell":"\u5355\u5143\u683c","Cell padding":"\u5355\u5143\u683c\u5185\u8fb9\u8ddd","Cell properties":"\u5355\u5143\u683c\u5c5e\u6027","Cell spacing":"\u5355\u5143\u683c\u5916\u95f4\u8ddd","Cell styles":"\u5355\u5143\u683c\u6837\u5f0f","Cell type":"\u50a8\u5b58\u683c\u522b","Center":"\u5c45\u4e2d","Characters":"\u5b57\u7b26","Characters (no spaces)":"\u5b57\u7b26(\u65e0\u7a7a\u683c)","Circle":"\u7a7a\u5fc3\u5706","Class":"\u7c7b\u578b","Clear formatting":"\u6e05\u9664\u683c\u5f0f","Close":"\u5173\u95ed","Code":"\u4ee3\u7801","Code sample...":"\u793a\u4f8b\u4ee3\u7801...","Code view":"\u4ee3\u7801\u89c6\u56fe","Color Picker":"\u9009\u8272\u5668","Color swatch":"\u989c\u8272\u6837\u672c","Cols":"\u5217","Column":"\u5217","Column clipboard actions":"\u5217\u526a\u8d34\u677f\u64cd\u4f5c","Column group":"\u5217\u7ec4","Column header":"\u5217\u6807\u9898","Constrain proportions":"\u4fdd\u6301\u6bd4\u4f8b","Copy":"\u590d\u5236","Copy column":"\u590d\u5236\u5217","Copy row":"\u590d\u5236\u884c","Could not find the specified string.":"\u672a\u627e\u5230\u641c\u7d22\u5185\u5bb9\u3002","Could not load emojis":"\u65e0\u6cd5\u52a0\u8f7dEmojis","Count":"\u8ba1\u6570","Currency":"\u8d27\u5e01","Current window":"\u5f53\u524d\u7a97\u53e3","Custom color":"\u81ea\u5b9a\u4e49\u989c\u8272","Custom...":"\u81ea\u5b9a\u4e49......","Cut":"\u526a\u5207","Cut column":"\u526a\u5207\u5217","Cut row":"\u526a\u5207\u884c","Dark Blue":"\u6df1\u84dd\u8272","Dark Gray":"\u6df1\u7070\u8272","Dark Green":"\u6df1\u7eff\u8272","Dark Orange":"\u6df1\u6a59\u8272","Dark Purple":"\u6df1\u7d2b\u8272","Dark Red":"\u6df1\u7ea2\u8272","Dark Turquoise":"\u6df1\u84dd\u7eff\u8272","Dark Yellow":"\u6697\u9ec4\u8272","Dashed":"\u865a\u7ebf","Date/time":"\u65e5\u671f/\u65f6\u95f4","Decrease indent":"\u51cf\u5c11\u7f29\u8fdb","Default":"\u9884\u8bbe","Delete accordion":"","Delete column":"\u5220\u9664\u5217","Delete row":"\u5220\u9664\u884c","Delete table":"\u5220\u9664\u8868\u683c","Dimensions":"\u5c3a\u5bf8","Disc":"\u5b9e\u5fc3\u5706","Div":"Div","Document":"\u6587\u6863","Dotted":"\u865a\u7ebf","Double":"\u53cc\u7cbe\u5ea6","Drop an image here":"\u62d6\u653e\u4e00\u5f20\u56fe\u50cf\u81f3\u6b64","Dropped file type is not supported":"\u6b64\u6587\u4ef6\u7c7b\u578b\u4e0d\u652f\u6301\u62d6\u653e","Edit":"\u7f16\u8f91","Embed":"\u5185\u5d4c","Emojis":"Emojis","Emojis...":"Emojis...","Error":"\u9519\u8bef","Error: Form submit field collision.":"\u9519\u8bef: \u8868\u5355\u63d0\u4ea4\u5b57\u6bb5\u51b2\u7a81\u3002","Error: No form element found.":"\u9519\u8bef: \u6ca1\u6709\u8868\u5355\u63a7\u4ef6\u3002","Extended Latin":"\u62c9\u4e01\u8bed\u6269\u5145","Failed to initialize plugin: {0}":"\u63d2\u4ef6\u521d\u59cb\u5316\u5931\u8d25: {0}","Failed to load plugin url: {0}":"\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25 \u94fe\u63a5: {0}","Failed to load plugin: {0} from url {1}":"\u63d2\u4ef6\u52a0\u8f7d\u5931\u8d25: {0} \u6765\u81ea\u94fe\u63a5 {1}","Failed to upload image: {0}":"\u56fe\u7247\u4e0a\u4f20\u5931\u8d25: {0}","File":"\u6587\u4ef6","Find":"\u5bfb\u627e","Find (if searchreplace plugin activated)":"\u67e5\u627e(\u5982\u679c\u67e5\u627e\u66ff\u6362\u63d2\u4ef6\u5df2\u6fc0\u6d3b)","Find and Replace":"\u67e5\u627e\u548c\u66ff\u6362","Find and replace...":"\u67e5\u627e\u5e76\u66ff\u6362...","Find in selection":"\u5728\u9009\u533a\u4e2d\u67e5\u627e","Find whole words only":"\u5168\u5b57\u5339\u914d","Flags":"\u65d7\u5e1c","Focus to contextual toolbar":"\u79fb\u52a8\u7126\u70b9\u5230\u4e0a\u4e0b\u6587\u83dc\u5355","Focus to element path":"\u79fb\u52a8\u7126\u70b9\u5230\u5143\u7d20\u8def\u5f84","Focus to menubar":"\u79fb\u52a8\u7126\u70b9\u5230\u83dc\u5355\u680f","Focus to toolbar":"\u79fb\u52a8\u7126\u70b9\u5230\u5de5\u5177\u680f","Font":"\u5b57\u4f53","Font size {0}":"","Font sizes":"\u5b57\u4f53\u5927\u5c0f","Font {0}":"","Fonts":"\u5b57\u4f53","Food and Drink":"\u98df\u7269\u548c\u996e\u54c1","Footer":"\u8868\u5c3e","Format":"\u683c\u5f0f","Format {0}":"","Formats":"\u683c\u5f0f","Fullscreen":"\u5168\u5c4f","G":"G","General":"\u4e00\u822c","Gray":"\u7070\u8272","Green":"\u7eff\u8272","Green component":"\u7eff\u8272\u90e8\u5206","Groove":"\u51f9\u69fd","Handy Shortcuts":"\u5feb\u6377\u952e","Header":"\u8868\u5934","Header cell":"\u8868\u5934\u5355\u5143\u683c","Heading 1":"\u4e00\u7ea7\u6807\u9898","Heading 2":"\u4e8c\u7ea7\u6807\u9898","Heading 3":"\u4e09\u7ea7\u6807\u9898","Heading 4":"\u56db\u7ea7\u6807\u9898","Heading 5":"\u4e94\u7ea7\u6807\u9898","Heading 6":"\u516d\u7ea7\u6807\u9898","Headings":"\u6807\u9898","Height":"\u9ad8\u5ea6","Help":"\u5e2e\u52a9","Hex color code":"\u5341\u516d\u8fdb\u5236\u989c\u8272\u4ee3\u7801","Hidden":"\u9690\u85cf","Horizontal align":"\u6c34\u5e73\u5bf9\u9f50","Horizontal line":"\u6c34\u5e73\u5206\u5272\u7ebf","Horizontal space":"\u6c34\u5e73\u95f4\u8ddd","ID":"ID","ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.":"ID\u5e94\u8be5\u4ee5\u82f1\u6587\u5b57\u6bcd\u5f00\u5934\uff0c\u540e\u9762\u53ea\u80fd\u6709\u82f1\u6587\u5b57\u6bcd\u3001\u6570\u5b57\u3001\u7834\u6298\u53f7\u3001\u70b9\u3001\u5192\u53f7\u6216\u4e0b\u5212\u7ebf\u3002","Image is decorative":"\u56fe\u50cf\u662f\u88c5\u9970\u6027\u7684","Image list":"\u56fe\u7247\u6e05\u5355","Image title":"\u56fe\u7247\u6807\u9898","Image...":"\u56fe\u7247...","ImageProxy HTTP error: Could not find Image Proxy":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u65e0\u6cd5\u627e\u5230\u56fe\u7247\u4ee3\u7406","ImageProxy HTTP error: Incorrect Image Proxy URL":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u56fe\u7247\u4ee3\u7406\u5730\u5740\u9519\u8bef","ImageProxy HTTP error: Rejected request":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u8bf7\u6c42\u88ab\u62d2\u7edd","ImageProxy HTTP error: Unknown ImageProxy error":"\u56fe\u7247\u4ee3\u7406\u8bf7\u6c42\u9519\u8bef\uff1a\u672a\u77e5\u7684\u56fe\u7247\u4ee3\u7406\u9519\u8bef","Increase indent":"\u589e\u52a0\u7f29\u8fdb","Inline":"\u6587\u672c","Insert":"\u63d2\u5165","Insert Template":"\u63d2\u5165\u6a21\u677f","Insert accordion":"","Insert column after":"\u5728\u53f3\u4fa7\u63d2\u5165\u5217","Insert column before":"\u5728\u5de6\u4fa7\u63d2\u5165\u5217","Insert date/time":"\u63d2\u5165\u65e5\u671f/\u65f6\u95f4","Insert image":"\u63d2\u5165\u56fe\u7247","Insert link (if link plugin activated)":"\u63d2\u5165\u94fe\u63a5 (\u5982\u679c\u94fe\u63a5\u63d2\u4ef6\u5df2\u6fc0\u6d3b)","Insert row after":"\u5728\u4e0b\u65b9\u63d2\u5165\u884c","Insert row before":"\u5728\u4e0a\u65b9\u63d2\u5165\u884c","Insert table":"\u63d2\u5165\u8868\u683c","Insert template...":"\u63d2\u5165\u6a21\u677f...","Insert video":"\u63d2\u5165\u89c6\u9891","Insert/Edit code sample":"\u63d2\u5165/\u7f16\u8f91\u4ee3\u7801\u793a\u4f8b","Insert/edit image":"\u63d2\u5165/\u7f16\u8f91\u56fe\u7247","Insert/edit link":"\u63d2\u5165/\u7f16\u8f91\u94fe\u63a5","Insert/edit media":"\u63d2\u5165/\u7f16\u8f91\u5a92\u4f53","Insert/edit video":"\u63d2\u5165/\u7f16\u8f91\u89c6\u9891","Inset":"\u5d4c\u5165","Invalid hex color code: {0}":"\u5341\u516d\u8fdb\u5236\u989c\u8272\u4ee3\u7801\u65e0\u6548\uff1a {0}","Invalid input":"\u65e0\u6548\u8f93\u5165","Italic":"\u659c\u4f53","Justify":"\u4e24\u7aef\u5bf9\u9f50","Keyboard Navigation":"\u952e\u76d8\u6307\u5f15","Language":"\u8bed\u8a00","Learn more...":"\u4e86\u89e3\u66f4\u591a...","Left":"\u5de6","Left to right":"\u7531\u5de6\u5230\u53f3","Light Blue":"\u6d45\u84dd\u8272","Light Gray":"\u6d45\u7070\u8272","Light Green":"\u6d45\u7eff\u8272","Light Purple":"\u6d45\u7d2b\u8272","Light Red":"\u6d45\u7ea2\u8272","Light Yellow":"\u6d45\u9ec4\u8272","Line height":"\u884c\u9ad8","Link list":"\u94fe\u63a5\u6e05\u5355","Link...":"\u94fe\u63a5...","List Properties":"\u5217\u8868\u5c5e\u6027","List properties...":"\u6807\u9898\u5b57\u4f53\u5c5e\u6027","Loading emojis...":"\u6b63\u5728\u52a0\u8f7dEmojis...","Loading...":"\u52a0\u8f7d\u4e2d...","Lower Alpha":"\u5c0f\u5199\u82f1\u6587\u5b57\u6bcd","Lower Greek":"\u5c0f\u5199\u5e0c\u814a\u5b57\u6bcd","Lower Roman":"\u5c0f\u5199\u7f57\u9a6c\u6570\u5b57","Match case":"\u5927\u5c0f\u5199\u5339\u914d","Mathematical":"\u6570\u5b66","Media poster (Image URL)":"\u5c01\u9762(\u56fe\u7247\u5730\u5740)","Media...":"\u591a\u5a92\u4f53...","Medium Blue":"\u4e2d\u84dd\u8272","Medium Gray":"\u4e2d\u7070\u8272","Medium Purple":"\u4e2d\u7d2b\u8272","Merge cells":"\u5408\u5e76\u5355\u5143\u683c","Middle":"\u5c45\u4e2d\u5bf9\u9f50","Midnight Blue":"\u6df1\u84dd\u8272","More...":"\u66f4\u591a...","Name":"\u540d\u79f0","Navy Blue":"\u6d77\u519b\u84dd","New document":"\u65b0\u5efa\u6587\u6863","New window":"\u65b0\u7a97\u53e3","Next":"\u4e0b\u4e00\u4e2a","No":"\u5426","No alignment":"\u672a\u5bf9\u9f50","No color":"\u65e0","Nonbreaking space":"\u4e0d\u95f4\u65ad\u7a7a\u683c","None":"\u65e0","Numbered list":"\u6709\u5e8f\u5217\u8868","OR":"\u6216","Objects":"\u7269\u4ef6","Ok":"\u786e\u5b9a","Open help dialog":"\u6253\u5f00\u5e2e\u52a9\u5bf9\u8bdd\u6846","Open link":"\u6253\u5f00\u94fe\u63a5","Open link in...":"\u94fe\u63a5\u6253\u5f00\u4f4d\u7f6e...","Open popup menu for split buttons":"\u6253\u5f00\u5f39\u51fa\u5f0f\u83dc\u5355\uff0c\u7528\u4e8e\u62c6\u5206\u6309\u94ae","Orange":"\u6a59\u8272","Outset":"\u5916\u7f6e","Page break":"\u5206\u9875\u7b26","Paragraph":"\u6bb5\u843d","Paste":"\u7c98\u8d34","Paste as text":"\u7c98\u8d34\u4e3a\u6587\u672c","Paste column after":"\u7c98\u8d34\u540e\u9762\u7684\u5217","Paste column before":"\u7c98\u8d34\u6b64\u5217\u524d","Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.":"\u5f53\u524d\u4e3a\u7eaf\u6587\u672c\u7c98\u8d34\u6a21\u5f0f\uff0c\u518d\u6b21\u70b9\u51fb\u53ef\u4ee5\u56de\u5230\u666e\u901a\u7c98\u8d34\u6a21\u5f0f\u3002","Paste or type a link":"\u7c98\u8d34\u6216\u8f93\u5165\u94fe\u63a5","Paste row after":"\u7c98\u8d34\u884c\u5230\u4e0b\u65b9","Paste row before":"\u7c98\u8d34\u884c\u5230\u4e0a\u65b9","Paste your embed code below:":"\u5c06\u5185\u5d4c\u4ee3\u7801\u7c98\u8d34\u5728\u4e0b\u9762:","People":"\u4eba\u7c7b","Plugins":"\u63d2\u4ef6","Plugins installed ({0}):":"\u5df2\u5b89\u88c5\u63d2\u4ef6 ({0}):","Powered by {0}":"\u7531{0}\u9a71\u52a8","Pre":"\u524d\u8a00","Preferences":"\u9996\u9009\u9879","Preformatted":"\u9884\u5148\u683c\u5f0f\u5316\u7684","Premium plugins:":"\u4f18\u79c0\u63d2\u4ef6\uff1a","Press the Up and Down arrow keys to resize the editor.":"","Press the arrow keys to resize the editor.":"","Press {0} for help":"","Preview":"\u9884\u89c8","Previous":"\u4e0a\u4e00\u4e2a","Print":"\u6253\u5370","Print...":"\u6253\u5370...","Purple":"\u7d2b\u8272","Quotations":"\u5f15\u7528","R":"R","Range 0 to 255":"\u8303\u56f40\u81f3255","Red":"\u7ea2\u8272","Red component":"\u7ea2\u8272\u90e8\u5206","Redo":"\u91cd\u505a","Remove":"\u79fb\u9664","Remove color":"\u79fb\u9664\u989c\u8272","Remove link":"\u79fb\u9664\u94fe\u63a5","Replace":"\u66ff\u6362","Replace all":"\u66ff\u6362\u5168\u90e8","Replace with":"\u66ff\u6362\u4e3a","Resize":"\u8c03\u6574\u5927\u5c0f","Restore last draft":"\u6062\u590d\u4e0a\u6b21\u7684\u8349\u7a3f","Reveal or hide additional toolbar items":"","Rich Text Area":"\u5bcc\u6587\u672c\u533a\u57df","Rich Text Area. Press ALT-0 for help.":"\u7f16\u8f91\u533a\u3002\u6309Alt+0\u952e\u6253\u5f00\u5e2e\u52a9\u3002","Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help":"\u7f16\u8f91\u533a\u3002\u6309ALT-F9\u6253\u5f00\u83dc\u5355\uff0c\u6309ALT-F10\u6253\u5f00\u5de5\u5177\u680f\uff0c\u6309ALT-0\u67e5\u770b\u5e2e\u52a9","Ridge":"\u6d77\u810a\u5ea7","Right":"\u53f3","Right to left":"\u7531\u53f3\u5230\u5de6","Row":"\u884c","Row clipboard actions":"\u884c\u526a\u8d34\u677f\u64cd\u4f5c","Row group":"\u884c\u7ec4","Row header":"\u884c\u5934","Row properties":"\u884c\u5c5e\u6027","Row type":"\u884c\u7c7b\u578b","Rows":"\u884c\u6570","Save":"\u4fdd\u5b58","Save (if save plugin activated)":"\u4fdd\u5b58(\u5982\u679c\u4fdd\u5b58\u63d2\u4ef6\u5df2\u6fc0\u6d3b)","Scope":"\u8303\u56f4","Search":"\u641c\u7d22","Select all":"\u5168\u9009","Select...":"\u9009\u62e9...","Selection":"\u9009\u62e9","Shortcut":"\u5feb\u6377\u65b9\u5f0f","Show blocks":"\u663e\u793a\u533a\u5757\u8fb9\u6846","Show caption":"\u663e\u793a\u6807\u9898","Show invisible characters":"\u663e\u793a\u4e0d\u53ef\u89c1\u5b57\u7b26","Size":"\u5b57\u53f7","Solid":"\u5b9e\u7ebf","Source":"\u6e90","Source code":"\u6e90\u4ee3\u7801","Special Character":"\u7279\u6b8a\u5b57\u7b26","Special character...":"\u7279\u6b8a\u5b57\u7b26...","Split cell":"\u62c6\u5206\u5355\u5143\u683c","Square":"\u5b9e\u5fc3\u65b9\u5757","Start list at number":"\u4ee5\u6570\u5b57\u5f00\u59cb\u5217\u8868","Strikethrough":"\u5220\u9664\u7ebf","Style":"\u6837\u5f0f","Subscript":"\u4e0b\u6807","Superscript":"\u4e0a\u6807","Switch to or from fullscreen mode":"\u5207\u6362\u5168\u5c4f\u6a21\u5f0f","Symbols":"\u7b26\u53f7","System Font":"\u7cfb\u7edf\u5b57\u4f53","Table":"\u8868\u683c","Table caption":"\u8868\u683c\u6807\u9898","Table properties":"\u8868\u683c\u5c5e\u6027","Table styles":"\u8868\u683c\u6837\u5f0f","Template":"\u6a21\u677f","Templates":"\u6a21\u677f","Text":"\u6587\u5b57","Text color":"\u6587\u672c\u989c\u8272","Text color {0}":"","Text to display":"\u8981\u663e\u793a\u7684\u6587\u672c","The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?":"\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u4e3a\u90ae\u4ef6\u5730\u5740\uff0c\u9700\u8981\u52a0\u4e0amailto: \u524d\u7f00\u5417\uff1f","The URL you entered seems to be an external link. Do you want to add the required http:// prefix?":"\u4f60\u6240\u586b\u5199\u7684URL\u5730\u5740\u5c5e\u4e8e\u5916\u90e8\u94fe\u63a5\uff0c\u9700\u8981\u52a0\u4e0ahttp:// \u524d\u7f00\u5417\uff1f","The URL you entered seems to be an external link. Do you want to add the required https:// prefix?":"\u60a8\u8f93\u5165\u7684 URL \u4f3c\u4e4e\u662f\u4e00\u4e2a\u5916\u90e8\u94fe\u63a5\u3002\u60a8\u60f3\u6dfb\u52a0\u6240\u9700\u7684 https:// \u524d\u7f00\u5417\uff1f","Title":"\u6807\u9898","To open the popup, press Shift+Enter":"\u6309Shitf+Enter\u952e\u6253\u5f00\u5bf9\u8bdd\u6846","Toggle accordion":"","Tools":"\u5de5\u5177","Top":"\u4e0a\u65b9\u5bf9\u9f50","Travel and Places":"\u65c5\u6e38\u548c\u5730\u70b9","Turquoise":"\u9752\u7eff\u8272","Underline":"\u4e0b\u5212\u7ebf","Undo":"\u64a4\u9500","Upload":"\u4e0a\u4f20","Uploading image":"\u4e0a\u4f20\u56fe\u7247","Upper Alpha":"\u5927\u5199\u82f1\u6587\u5b57\u6bcd","Upper Roman":"\u5927\u5199\u7f57\u9a6c\u6570\u5b57","Url":"\u5730\u5740","User Defined":"\u81ea\u5b9a\u4e49","Valid":"\u6709\u6548","Version":"\u7248\u672c","Vertical align":"\u5782\u76f4\u5bf9\u9f50","Vertical space":"\u5782\u76f4\u95f4\u8ddd","View":"\u67e5\u770b","Visual aids":"\u7f51\u683c\u7ebf","Warn":"\u8b66\u544a","White":"\u767d\u8272","Width":"\u5bbd\u5ea6","Word count":"\u5b57\u6570","Words":"\u5355\u8bcd","Words: {0}":"\u5b57\u6570\uff1a{0}","Yellow":"\u9ec4\u8272","Yes":"\u662f","You are using {0}":"\u4f60\u6b63\u5728\u4f7f\u7528 {0}","You have unsaved changes are you sure you want to navigate away?":"\u4f60\u8fd8\u6709\u6587\u6863\u5c1a\u672a\u4fdd\u5b58\uff0c\u786e\u5b9a\u8981\u79bb\u5f00\uff1f","Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.":"\u4f60\u7684\u6d4f\u89c8\u5668\u4e0d\u652f\u6301\u6253\u5f00\u526a\u8d34\u677f\uff0c\u8bf7\u4f7f\u7528Ctrl+X/C/V\u7b49\u5feb\u6377\u952e\u3002","alignment":"\u5bf9\u9f50","austral sign":"\u6fb3\u5143\u7b26\u53f7","cedi sign":"\u585e\u5730\u7b26\u53f7","colon sign":"\u5192\u53f7","cruzeiro sign":"\u514b\u9c81\u8d5b\u7f57\u5e01\u7b26\u53f7","currency sign":"\u8d27\u5e01\u7b26\u53f7","dollar sign":"\u7f8e\u5143\u7b26\u53f7","dong sign":"\u8d8a\u5357\u76fe\u7b26\u53f7","drachma sign":"\u5fb7\u62c9\u514b\u9a6c\u7b26\u53f7","euro-currency sign":"\u6b27\u5143\u7b26\u53f7","example":"\u793a\u4f8b","formatting":"\u683c\u5f0f\u5316","french franc sign":"\u6cd5\u90ce\u7b26\u53f7","german penny symbol":"\u5fb7\u56fd\u4fbf\u58eb\u7b26\u53f7","guarani sign":"\u74dc\u62c9\u5c3c\u7b26\u53f7","history":"\u5386\u53f2","hryvnia sign":"\u683c\u91cc\u592b\u5c3c\u4e9a\u7b26\u53f7","indentation":"\u7f29\u8fdb","indian rupee sign":"\u5370\u5ea6\u5362\u6bd4","kip sign":"\u8001\u631d\u57fa\u666e\u7b26\u53f7","lira sign":"\u91cc\u62c9\u7b26\u53f7","livre tournois sign":"\u91cc\u5f17\u5f17\u5c14\u7b26\u53f7","manat sign":"\u9a6c\u7eb3\u7279\u7b26\u53f7","mill sign":"\u5bc6\u5c14\u7b26\u53f7","naira sign":"\u5948\u62c9\u7b26\u53f7","new sheqel sign":"\u65b0\u8c22\u514b\u5c14\u7b26\u53f7","nordic mark sign":"\u5317\u6b27\u9a6c\u514b","peseta sign":"\u6bd4\u585e\u5854\u7b26\u53f7","peso sign":"\u6bd4\u7d22\u7b26\u53f7","ruble sign":"\u5362\u5e03\u7b26\u53f7","rupee sign":"\u5362\u6bd4\u7b26\u53f7","spesmilo sign":"spesmilo\u7b26\u53f7","styles":"\u6837\u5f0f","tenge sign":"\u575a\u6208\u7b26\u53f7","tugrik sign":"\u56fe\u683c\u91cc\u514b\u7b26\u53f7","turkish lira sign":"\u571f\u8033\u5176\u91cc\u62c9","won sign":"\u97e9\u5143\u7b26\u53f7","yen character":"\u65e5\u5143\u5b57\u6837","yen/yuan character variant one":"\u5143\u5b57\u6837\uff08\u5927\u5199\uff09","yuan character":"\u4eba\u6c11\u5e01\u5143\u5b57\u6837","yuan character, in hong kong and taiwan":"\u5143\u5b57\u6837\uff08\u6e2f\u53f0\u5730\u533a\uff09","{0} characters":"{0} \u4e2a\u5b57\u7b26","{0} columns, {1} rows":"","{0} words":"{0} \u5b57"}); ================================================ FILE: apps/web-antd/public/tinymce/plugins/emoticons/js/emojiimages.js ================================================ window.tinymce.Resource.add("tinymce.plugins.emoticons",{100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:'💯',fitzpatrick_scale:false,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:'🔢',fitzpatrick_scale:false,category:"symbols"},grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:'😀',fitzpatrick_scale:false,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:'😬',fitzpatrick_scale:false,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:'😁',fitzpatrick_scale:false,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:'😂',fitzpatrick_scale:false,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:'🤣',fitzpatrick_scale:false,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:'🥳',fitzpatrick_scale:false,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:'😃',fitzpatrick_scale:false,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:'😄',fitzpatrick_scale:false,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:'😅',fitzpatrick_scale:false,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:'😆',fitzpatrick_scale:false,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:'😇',fitzpatrick_scale:false,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:'😉',fitzpatrick_scale:false,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:'😊',fitzpatrick_scale:false,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:'🙂',fitzpatrick_scale:false,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:'🙃',fitzpatrick_scale:false,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:'☺️',fitzpatrick_scale:false,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:'😋',fitzpatrick_scale:false,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:'😌',fitzpatrick_scale:false,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:'😍',fitzpatrick_scale:false,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:'🥰',fitzpatrick_scale:false,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'😘',fitzpatrick_scale:false,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:'😗',fitzpatrick_scale:false,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:'😙',fitzpatrick_scale:false,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:'😚',fitzpatrick_scale:false,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:'😜',fitzpatrick_scale:false,category:"people"},zany:{keywords:["face","goofy","crazy"],char:'🤪',fitzpatrick_scale:false,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:'🤨',fitzpatrick_scale:false,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:'🧐',fitzpatrick_scale:false,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:'😝',fitzpatrick_scale:false,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:'😛',fitzpatrick_scale:false,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:'🤑',fitzpatrick_scale:false,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:'🤓',fitzpatrick_scale:false,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:'😎',fitzpatrick_scale:false,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:'🤩',fitzpatrick_scale:false,category:"people"},clown_face:{keywords:["face"],char:'🤡',fitzpatrick_scale:false,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:'🤠',fitzpatrick_scale:false,category:"people"},hugs:{keywords:["face","smile","hug"],char:'🤗',fitzpatrick_scale:false,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:'😏',fitzpatrick_scale:false,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:'😶',fitzpatrick_scale:false,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:'😐',fitzpatrick_scale:false,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:'😑',fitzpatrick_scale:false,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:'😒',fitzpatrick_scale:false,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:'🙄',fitzpatrick_scale:false,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:'🤔',fitzpatrick_scale:false,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:'🤥',fitzpatrick_scale:false,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:'🤭',fitzpatrick_scale:false,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:'🤫',fitzpatrick_scale:false,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:'🤬',fitzpatrick_scale:false,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:'🤯',fitzpatrick_scale:false,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:'😳',fitzpatrick_scale:false,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:'😞',fitzpatrick_scale:false,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:'😟',fitzpatrick_scale:false,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:'😠',fitzpatrick_scale:false,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:'😡',fitzpatrick_scale:false,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:'😔',fitzpatrick_scale:false,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:'😕',fitzpatrick_scale:false,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:'🙁',fitzpatrick_scale:false,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:'☹',fitzpatrick_scale:false,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:'😣',fitzpatrick_scale:false,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:'😖',fitzpatrick_scale:false,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:'😫',fitzpatrick_scale:false,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:'😩',fitzpatrick_scale:false,category:"people"},pleading:{keywords:["face","begging","mercy"],char:'🥺',fitzpatrick_scale:false,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:'😤',fitzpatrick_scale:false,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:'😮',fitzpatrick_scale:false,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:'😱',fitzpatrick_scale:false,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:'😨',fitzpatrick_scale:false,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:'😰',fitzpatrick_scale:false,category:"people"},hushed:{keywords:["face","woo","shh"],char:'😯',fitzpatrick_scale:false,category:"people"},frowning:{keywords:["face","aw","what"],char:'😦',fitzpatrick_scale:false,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:'😧',fitzpatrick_scale:false,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:'😢',fitzpatrick_scale:false,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:'😥',fitzpatrick_scale:false,category:"people"},drooling_face:{keywords:["face"],char:'🤤',fitzpatrick_scale:false,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:'😪',fitzpatrick_scale:false,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:'😓',fitzpatrick_scale:false,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:'🥵',fitzpatrick_scale:false,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:'🥶',fitzpatrick_scale:false,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:'😭',fitzpatrick_scale:false,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:'😵',fitzpatrick_scale:false,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:'😲',fitzpatrick_scale:false,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:'🤐',fitzpatrick_scale:false,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:'🤢',fitzpatrick_scale:false,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:'🤧',fitzpatrick_scale:false,category:"people"},vomiting:{keywords:["face","sick"],char:'🤮',fitzpatrick_scale:false,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:'😷',fitzpatrick_scale:false,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:'🤒',fitzpatrick_scale:false,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:'🤕',fitzpatrick_scale:false,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:'🥴',fitzpatrick_scale:false,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:'😴',fitzpatrick_scale:false,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:'💤',fitzpatrick_scale:false,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:'💩',fitzpatrick_scale:false,category:"people"},smiling_imp:{keywords:["devil","horns"],char:'😈',fitzpatrick_scale:false,category:"people"},imp:{keywords:["devil","angry","horns"],char:'👿',fitzpatrick_scale:false,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:'👹',fitzpatrick_scale:false,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:'👺',fitzpatrick_scale:false,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:'💀',fitzpatrick_scale:false,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:'👻',fitzpatrick_scale:false,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:'👽',fitzpatrick_scale:false,category:"people"},robot:{keywords:["computer","machine","bot"],char:'🤖',fitzpatrick_scale:false,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:'😺',fitzpatrick_scale:false,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:'😸',fitzpatrick_scale:false,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:'😹',fitzpatrick_scale:false,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:'😻',fitzpatrick_scale:false,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:'😼',fitzpatrick_scale:false,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:'😽',fitzpatrick_scale:false,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:'🙀',fitzpatrick_scale:false,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:'😿',fitzpatrick_scale:false,category:"people"},pouting_cat:{keywords:["animal","cats"],char:'😾',fitzpatrick_scale:false,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:'🤲',fitzpatrick_scale:true,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:'🙌',fitzpatrick_scale:true,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:'👏',fitzpatrick_scale:true,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:'👋',fitzpatrick_scale:true,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:'🤙',fitzpatrick_scale:true,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:'👍',fitzpatrick_scale:true,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:'👎',fitzpatrick_scale:true,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:'👊',fitzpatrick_scale:true,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:'✊',fitzpatrick_scale:true,category:"people"},fist_left:{keywords:["hand","fistbump"],char:'🤛',fitzpatrick_scale:true,category:"people"},fist_right:{keywords:["hand","fistbump"],char:'🤜',fitzpatrick_scale:true,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:'✌',fitzpatrick_scale:true,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:'👌',fitzpatrick_scale:true,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:'✋',fitzpatrick_scale:true,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:'🤚',fitzpatrick_scale:true,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:'👐',fitzpatrick_scale:true,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:'💪',fitzpatrick_scale:true,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:'🙏',fitzpatrick_scale:true,category:"people"},foot:{keywords:["kick","stomp"],char:'🦶',fitzpatrick_scale:true,category:"people"},leg:{keywords:["kick","limb"],char:'🦵',fitzpatrick_scale:true,category:"people"},handshake:{keywords:["agreement","shake"],char:'🤝',fitzpatrick_scale:false,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:'☝',fitzpatrick_scale:true,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:'👆',fitzpatrick_scale:true,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:'👇',fitzpatrick_scale:true,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:'👈',fitzpatrick_scale:true,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:'👉',fitzpatrick_scale:true,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:'🖕',fitzpatrick_scale:true,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:'🖐',fitzpatrick_scale:true,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:'🤟',fitzpatrick_scale:true,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:'🤘',fitzpatrick_scale:true,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:'🤞',fitzpatrick_scale:true,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:'🖖',fitzpatrick_scale:true,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:'✍',fitzpatrick_scale:true,category:"people"},selfie:{keywords:["camera","phone"],char:'🤳',fitzpatrick_scale:true,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:'💅',fitzpatrick_scale:true,category:"people"},lips:{keywords:["mouth","kiss"],char:'👄',fitzpatrick_scale:false,category:"people"},tooth:{keywords:["teeth","dentist"],char:'🦷',fitzpatrick_scale:false,category:"people"},tongue:{keywords:["mouth","playful"],char:'👅',fitzpatrick_scale:false,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:'👂',fitzpatrick_scale:true,category:"people"},nose:{keywords:["smell","sniff"],char:'👃',fitzpatrick_scale:true,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:'👁',fitzpatrick_scale:false,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:'👀',fitzpatrick_scale:false,category:"people"},brain:{keywords:["smart","intelligent"],char:'🧠',fitzpatrick_scale:false,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:'👤',fitzpatrick_scale:false,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:'👥',fitzpatrick_scale:false,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:'🗣',fitzpatrick_scale:false,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:'👶',fitzpatrick_scale:true,category:"people"},child:{keywords:["gender-neutral","young"],char:'🧒',fitzpatrick_scale:true,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:'👦',fitzpatrick_scale:true,category:"people"},girl:{keywords:["female","woman","teenager"],char:'👧',fitzpatrick_scale:true,category:"people"},adult:{keywords:["gender-neutral","person"],char:'🧑',fitzpatrick_scale:true,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:'👨',fitzpatrick_scale:true,category:"people"},woman:{keywords:["female","girls","lady"],char:'👩',fitzpatrick_scale:true,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:'👱‍♀️',fitzpatrick_scale:true,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:'👱',fitzpatrick_scale:true,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:'🧔',fitzpatrick_scale:true,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:'🧓',fitzpatrick_scale:true,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:'👴',fitzpatrick_scale:true,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:'👵',fitzpatrick_scale:true,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:'👲',fitzpatrick_scale:true,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:'🧕',fitzpatrick_scale:true,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:'👳‍♀️',fitzpatrick_scale:true,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:'👳',fitzpatrick_scale:true,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:'👮‍♀️',fitzpatrick_scale:true,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:'👮',fitzpatrick_scale:true,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:'👷‍♀️',fitzpatrick_scale:true,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:'👷',fitzpatrick_scale:true,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:'💂‍♀️',fitzpatrick_scale:true,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:'💂',fitzpatrick_scale:true,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:'🕵️‍♀️',fitzpatrick_scale:true,category:"people"},male_detective:{keywords:["human","spy","detective"],char:'🕵',fitzpatrick_scale:true,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:'👩‍⚕️',fitzpatrick_scale:true,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:'👨‍⚕️',fitzpatrick_scale:true,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:'👩‍🌾',fitzpatrick_scale:true,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:'👨‍🌾',fitzpatrick_scale:true,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:'👩‍🍳',fitzpatrick_scale:true,category:"people"},man_cook:{keywords:["chef","man","human"],char:'👨‍🍳',fitzpatrick_scale:true,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:'👩‍🎓',fitzpatrick_scale:true,category:"people"},man_student:{keywords:["graduate","man","human"],char:'👨‍🎓',fitzpatrick_scale:true,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:'👩‍🎤',fitzpatrick_scale:true,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:'👨‍🎤',fitzpatrick_scale:true,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:'👩‍🏫',fitzpatrick_scale:true,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:'👨‍🏫',fitzpatrick_scale:true,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:'👩‍🏭',fitzpatrick_scale:true,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:'👨‍🏭',fitzpatrick_scale:true,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:'👩‍💻',fitzpatrick_scale:true,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:'👨‍💻',fitzpatrick_scale:true,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:'👩‍💼',fitzpatrick_scale:true,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:'👨‍💼',fitzpatrick_scale:true,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:'👩‍🔧',fitzpatrick_scale:true,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:'👨‍🔧',fitzpatrick_scale:true,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:'👩‍🔬',fitzpatrick_scale:true,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:'👨‍🔬',fitzpatrick_scale:true,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:'👩‍🎨',fitzpatrick_scale:true,category:"people"},man_artist:{keywords:["painter","man","human"],char:'👨‍🎨',fitzpatrick_scale:true,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:'👩‍🚒',fitzpatrick_scale:true,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:'👨‍🚒',fitzpatrick_scale:true,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:'👩‍✈️',fitzpatrick_scale:true,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:'👨‍✈️',fitzpatrick_scale:true,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:'👩‍🚀',fitzpatrick_scale:true,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:'👨‍🚀',fitzpatrick_scale:true,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:'👩‍⚖️',fitzpatrick_scale:true,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:'👨‍⚖️',fitzpatrick_scale:true,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:'🦸‍♀️',fitzpatrick_scale:true,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:'🦸‍♂️',fitzpatrick_scale:true,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:'🦹‍♀️',fitzpatrick_scale:true,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:'🦹‍♂️',fitzpatrick_scale:true,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:'🤶',fitzpatrick_scale:true,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:'🎅',fitzpatrick_scale:true,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:'🧙‍♀️',fitzpatrick_scale:true,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:'🧙‍♂️',fitzpatrick_scale:true,category:"people"},woman_elf:{keywords:["woman","female"],char:'🧝‍♀️',fitzpatrick_scale:true,category:"people"},man_elf:{keywords:["man","male"],char:'🧝‍♂️',fitzpatrick_scale:true,category:"people"},woman_vampire:{keywords:["woman","female"],char:'🧛‍♀️',fitzpatrick_scale:true,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:'🧛‍♂️',fitzpatrick_scale:true,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:'🧟‍♀️',fitzpatrick_scale:false,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:'🧟‍♂️',fitzpatrick_scale:false,category:"people"},woman_genie:{keywords:["woman","female"],char:'🧞‍♀️',fitzpatrick_scale:false,category:"people"},man_genie:{keywords:["man","male"],char:'🧞‍♂️',fitzpatrick_scale:false,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:'🧜‍♀️',fitzpatrick_scale:true,category:"people"},merman:{keywords:["man","male","triton"],char:'🧜‍♂️',fitzpatrick_scale:true,category:"people"},woman_fairy:{keywords:["woman","female"],char:'🧚‍♀️',fitzpatrick_scale:true,category:"people"},man_fairy:{keywords:["man","male"],char:'🧚‍♂️',fitzpatrick_scale:true,category:"people"},angel:{keywords:["heaven","wings","halo"],char:'👼',fitzpatrick_scale:true,category:"people"},pregnant_woman:{keywords:["baby"],char:'🤰',fitzpatrick_scale:true,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:'🤱',fitzpatrick_scale:true,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:'👸',fitzpatrick_scale:true,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:'🤴',fitzpatrick_scale:true,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:'👰',fitzpatrick_scale:true,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:'🤵',fitzpatrick_scale:true,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:'🏃‍♀️',fitzpatrick_scale:true,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:'🏃',fitzpatrick_scale:true,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:'🚶‍♀️',fitzpatrick_scale:true,category:"people"},walking_man:{keywords:["human","feet","steps"],char:'🚶',fitzpatrick_scale:true,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:'💃',fitzpatrick_scale:true,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:'🕺',fitzpatrick_scale:true,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:'👯',fitzpatrick_scale:false,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:'👯‍♂️',fitzpatrick_scale:false,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:'👫',fitzpatrick_scale:false,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:'👬',fitzpatrick_scale:false,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:'👭',fitzpatrick_scale:false,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:'🙇‍♀️',fitzpatrick_scale:true,category:"people"},bowing_man:{keywords:["man","male","boy"],char:'🙇',fitzpatrick_scale:true,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:'🤦‍♂️',fitzpatrick_scale:true,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:'🤦‍♀️',fitzpatrick_scale:true,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:'🤷',fitzpatrick_scale:true,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:'🤷‍♂️',fitzpatrick_scale:true,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:'💁',fitzpatrick_scale:true,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:'💁‍♂️',fitzpatrick_scale:true,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:'🙅',fitzpatrick_scale:true,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:'🙅‍♂️',fitzpatrick_scale:true,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:'🙆',fitzpatrick_scale:true,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:'🙆‍♂️',fitzpatrick_scale:true,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:'🙋',fitzpatrick_scale:true,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:'🙋‍♂️',fitzpatrick_scale:true,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:'🙎',fitzpatrick_scale:true,category:"people"},pouting_man:{keywords:["male","boy","man"],char:'🙎‍♂️',fitzpatrick_scale:true,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:'🙍',fitzpatrick_scale:true,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:'🙍‍♂️',fitzpatrick_scale:true,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:'💇',fitzpatrick_scale:true,category:"people"},haircut_man:{keywords:["male","boy","man"],char:'💇‍♂️',fitzpatrick_scale:true,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:'💆',fitzpatrick_scale:true,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:'💆‍♂️',fitzpatrick_scale:true,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:'🧖‍♀️',fitzpatrick_scale:true,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:'🧖‍♂️',fitzpatrick_scale:true,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'💑',fitzpatrick_scale:false,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'👩‍❤️‍👩',fitzpatrick_scale:false,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:'👨‍❤️‍👨',fitzpatrick_scale:false,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'💏',fitzpatrick_scale:false,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:'👩‍❤️‍💋‍👩',fitzpatrick_scale:false,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:'👨‍❤️‍💋‍👨',fitzpatrick_scale:false,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:'👪',fitzpatrick_scale:false,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:'👨‍👩‍👧',fitzpatrick_scale:false,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👩‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👩‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'👨‍👩‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👦',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👧',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:'👩‍👩‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👦',fitzpatrick_scale:false,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👧',fitzpatrick_scale:false,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:'👨‍👨‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:'👩‍👦',fitzpatrick_scale:false,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:'👩‍👧',fitzpatrick_scale:false,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:'👩‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:'👩‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:'👩‍👧‍👧',fitzpatrick_scale:false,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:'👨‍👦',fitzpatrick_scale:false,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:'👨‍👧',fitzpatrick_scale:false,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:'👨‍👧‍👦',fitzpatrick_scale:false,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:'👨‍👦‍👦',fitzpatrick_scale:false,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:'👨‍👧‍👧',fitzpatrick_scale:false,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:'🧶',fitzpatrick_scale:false,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:'🧵',fitzpatrick_scale:false,category:"people"},coat:{keywords:["jacket"],char:'🧥',fitzpatrick_scale:false,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:'🥼',fitzpatrick_scale:false,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:'👚',fitzpatrick_scale:false,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:'👕',fitzpatrick_scale:false,category:"people"},jeans:{keywords:["fashion","shopping"],char:'👖',fitzpatrick_scale:false,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:'👔',fitzpatrick_scale:false,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:'👗',fitzpatrick_scale:false,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:'👙',fitzpatrick_scale:false,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:'👘',fitzpatrick_scale:false,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:'💄',fitzpatrick_scale:false,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:'💋',fitzpatrick_scale:false,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:'👣',fitzpatrick_scale:false,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:'🥿',fitzpatrick_scale:false,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:'👠',fitzpatrick_scale:false,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:'👡',fitzpatrick_scale:false,category:"people"},boot:{keywords:["shoes","fashion"],char:'👢',fitzpatrick_scale:false,category:"people"},mans_shoe:{keywords:["fashion","male"],char:'👞',fitzpatrick_scale:false,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:'👟',fitzpatrick_scale:false,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:'🥾',fitzpatrick_scale:false,category:"people"},socks:{keywords:["stockings","clothes"],char:'🧦',fitzpatrick_scale:false,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:'🧤',fitzpatrick_scale:false,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:'🧣',fitzpatrick_scale:false,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:'👒',fitzpatrick_scale:false,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:'🎩',fitzpatrick_scale:false,category:"people"},billed_hat:{keywords:["cap","baseball"],char:'🧢',fitzpatrick_scale:false,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:'⛑',fitzpatrick_scale:false,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:'🎓',fitzpatrick_scale:false,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:'👑',fitzpatrick_scale:false,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:'🎒',fitzpatrick_scale:false,category:"people"},luggage:{keywords:["packing","travel"],char:'🧳',fitzpatrick_scale:false,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:'👝',fitzpatrick_scale:false,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:'👛',fitzpatrick_scale:false,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:'👜',fitzpatrick_scale:false,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:'💼',fitzpatrick_scale:false,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:'👓',fitzpatrick_scale:false,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:'🕶',fitzpatrick_scale:false,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:'🥽',fitzpatrick_scale:false,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:'💍',fitzpatrick_scale:false,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:'🌂',fitzpatrick_scale:false,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:'🐶',fitzpatrick_scale:false,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:'🐱',fitzpatrick_scale:false,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:'🐭',fitzpatrick_scale:false,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:'🐹',fitzpatrick_scale:false,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:'🐰',fitzpatrick_scale:false,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:'🦊',fitzpatrick_scale:false,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:'🐻',fitzpatrick_scale:false,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:'🐼',fitzpatrick_scale:false,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:'🐨',fitzpatrick_scale:false,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:'🐯',fitzpatrick_scale:false,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:'🦁',fitzpatrick_scale:false,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:'🐮',fitzpatrick_scale:false,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:'🐷',fitzpatrick_scale:false,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:'🐽',fitzpatrick_scale:false,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:'🐸',fitzpatrick_scale:false,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:'🦑',fitzpatrick_scale:false,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:'🐙',fitzpatrick_scale:false,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:'🦐',fitzpatrick_scale:false,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:'🐵',fitzpatrick_scale:false,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:'🦍',fitzpatrick_scale:false,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:'🙈',fitzpatrick_scale:false,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:'🙉',fitzpatrick_scale:false,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:'🙊',fitzpatrick_scale:false,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:'🐒',fitzpatrick_scale:false,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:'🐔',fitzpatrick_scale:false,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:'🐧',fitzpatrick_scale:false,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:'🐦',fitzpatrick_scale:false,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:'🐤',fitzpatrick_scale:false,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:'🐣',fitzpatrick_scale:false,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:'🐥',fitzpatrick_scale:false,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:'🦆',fitzpatrick_scale:false,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:'🦅',fitzpatrick_scale:false,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:'🦉',fitzpatrick_scale:false,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:'🦇',fitzpatrick_scale:false,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:'🐺',fitzpatrick_scale:false,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:'🐗',fitzpatrick_scale:false,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:'🐴',fitzpatrick_scale:false,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:'🦄',fitzpatrick_scale:false,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:'🐝',fitzpatrick_scale:false,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:'🐛',fitzpatrick_scale:false,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:'🦋',fitzpatrick_scale:false,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:'🐌',fitzpatrick_scale:false,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:'🐞',fitzpatrick_scale:false,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:'🐜',fitzpatrick_scale:false,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:'🦗',fitzpatrick_scale:false,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:'🕷',fitzpatrick_scale:false,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:'🦂',fitzpatrick_scale:false,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:'🦀',fitzpatrick_scale:false,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:'🐍',fitzpatrick_scale:false,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:'🦎',fitzpatrick_scale:false,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:'🦖',fitzpatrick_scale:false,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:'🦕',fitzpatrick_scale:false,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:'🐢',fitzpatrick_scale:false,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:'🐠',fitzpatrick_scale:false,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:'🐟',fitzpatrick_scale:false,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:'🐡',fitzpatrick_scale:false,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:'🐬',fitzpatrick_scale:false,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:'🦈',fitzpatrick_scale:false,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:'🐳',fitzpatrick_scale:false,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:'🐋',fitzpatrick_scale:false,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:'🐊',fitzpatrick_scale:false,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:'🐆',fitzpatrick_scale:false,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:'🦓',fitzpatrick_scale:false,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:'🐅',fitzpatrick_scale:false,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:'🐃',fitzpatrick_scale:false,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:'🐂',fitzpatrick_scale:false,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:'🐄',fitzpatrick_scale:false,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:'🦌',fitzpatrick_scale:false,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:'🐪',fitzpatrick_scale:false,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:'🐫',fitzpatrick_scale:false,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:'🦒',fitzpatrick_scale:false,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:'🐘',fitzpatrick_scale:false,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:'🦏',fitzpatrick_scale:false,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:'🐐',fitzpatrick_scale:false,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:'🐏',fitzpatrick_scale:false,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:'🐑',fitzpatrick_scale:false,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:'🐎',fitzpatrick_scale:false,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:'🐖',fitzpatrick_scale:false,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:'🐀',fitzpatrick_scale:false,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:'🐁',fitzpatrick_scale:false,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:'🐓',fitzpatrick_scale:false,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:'🦃',fitzpatrick_scale:false,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:'🕊',fitzpatrick_scale:false,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:'🐕',fitzpatrick_scale:false,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:'🐩',fitzpatrick_scale:false,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:'🐈',fitzpatrick_scale:false,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:'🐇',fitzpatrick_scale:false,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:'🐿',fitzpatrick_scale:false,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:'🦔',fitzpatrick_scale:false,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:'🦝',fitzpatrick_scale:false,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:'🦙',fitzpatrick_scale:false,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:'🦛',fitzpatrick_scale:false,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:'🦘',fitzpatrick_scale:false,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:'🦡',fitzpatrick_scale:false,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:'🦢',fitzpatrick_scale:false,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:'🦚',fitzpatrick_scale:false,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:'🦜',fitzpatrick_scale:false,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:'🦞',fitzpatrick_scale:false,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:'🦟',fitzpatrick_scale:false,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:'🐾',fitzpatrick_scale:false,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:'🐉',fitzpatrick_scale:false,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:'🐲',fitzpatrick_scale:false,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:'🌵',fitzpatrick_scale:false,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:'🎄',fitzpatrick_scale:false,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:'🌲',fitzpatrick_scale:false,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:'🌳',fitzpatrick_scale:false,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:'🌴',fitzpatrick_scale:false,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:'🌱',fitzpatrick_scale:false,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:'🌿',fitzpatrick_scale:false,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:'☘',fitzpatrick_scale:false,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:'🍀',fitzpatrick_scale:false,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:'🎍',fitzpatrick_scale:false,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:'🎋',fitzpatrick_scale:false,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:'🍃',fitzpatrick_scale:false,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:'🍂',fitzpatrick_scale:false,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:'🍁',fitzpatrick_scale:false,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:'🌾',fitzpatrick_scale:false,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:'🌺',fitzpatrick_scale:false,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:'🌻',fitzpatrick_scale:false,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:'🌹',fitzpatrick_scale:false,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:'🥀',fitzpatrick_scale:false,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:'🌷',fitzpatrick_scale:false,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:'🌼',fitzpatrick_scale:false,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:'🌸',fitzpatrick_scale:false,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:'💐',fitzpatrick_scale:false,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:'🍄',fitzpatrick_scale:false,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:'🌰',fitzpatrick_scale:false,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:'🎃',fitzpatrick_scale:false,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:'🐚',fitzpatrick_scale:false,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:'🕸',fitzpatrick_scale:false,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:'🌎',fitzpatrick_scale:false,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:'🌍',fitzpatrick_scale:false,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:'🌏',fitzpatrick_scale:false,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:'🌕',fitzpatrick_scale:false,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:'🌖',fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌗',fitzpatrick_scale:false,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌘',fitzpatrick_scale:false,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌑',fitzpatrick_scale:false,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌒',fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌓',fitzpatrick_scale:false,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:'🌔',fitzpatrick_scale:false,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌚',fitzpatrick_scale:false,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌝',fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌛',fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:'🌜',fitzpatrick_scale:false,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:'🌞',fitzpatrick_scale:false,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:'🌙',fitzpatrick_scale:false,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:'⭐',fitzpatrick_scale:false,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:'🌟',fitzpatrick_scale:false,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:'💫',fitzpatrick_scale:false,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:'✨',fitzpatrick_scale:false,category:"animals_and_nature"},comet:{keywords:["space"],char:'☄',fitzpatrick_scale:false,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:'☀️',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:'🌤',fitzpatrick_scale:false,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:'⛅',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:'🌥',fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:'🌦',fitzpatrick_scale:false,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:'☁️',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:'🌧',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:'⛈',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:'🌩',fitzpatrick_scale:false,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:'⚡',fitzpatrick_scale:false,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:'🔥',fitzpatrick_scale:false,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:'💥',fitzpatrick_scale:false,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:'❄️',fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:'🌨',fitzpatrick_scale:false,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:'⛄',fitzpatrick_scale:false,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:'☃',fitzpatrick_scale:false,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:'🌬',fitzpatrick_scale:false,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:'💨',fitzpatrick_scale:false,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:'🌪',fitzpatrick_scale:false,category:"animals_and_nature"},fog:{keywords:["weather"],char:'🌫',fitzpatrick_scale:false,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:'☂',fitzpatrick_scale:false,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:'☔',fitzpatrick_scale:false,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:'💧',fitzpatrick_scale:false,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:'💦',fitzpatrick_scale:false,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:'🌊',fitzpatrick_scale:false,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:'🍏',fitzpatrick_scale:false,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:'🍎',fitzpatrick_scale:false,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:'🍐',fitzpatrick_scale:false,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:'🍊',fitzpatrick_scale:false,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:'🍋',fitzpatrick_scale:false,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:'🍌',fitzpatrick_scale:false,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:'🍉',fitzpatrick_scale:false,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:'🍇',fitzpatrick_scale:false,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:'🍓',fitzpatrick_scale:false,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:'🍈',fitzpatrick_scale:false,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:'🍒',fitzpatrick_scale:false,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:'🍑',fitzpatrick_scale:false,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:'🍍',fitzpatrick_scale:false,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:'🥥',fitzpatrick_scale:false,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:'🥝',fitzpatrick_scale:false,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:'🥭',fitzpatrick_scale:false,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:'🥑',fitzpatrick_scale:false,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:'🥦',fitzpatrick_scale:false,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:'🍅',fitzpatrick_scale:false,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:'🍆',fitzpatrick_scale:false,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:'🥒',fitzpatrick_scale:false,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:'🥕',fitzpatrick_scale:false,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:'🌶',fitzpatrick_scale:false,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:'🥔',fitzpatrick_scale:false,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:'🌽',fitzpatrick_scale:false,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:'🥬',fitzpatrick_scale:false,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:'🍠',fitzpatrick_scale:false,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:'🥜',fitzpatrick_scale:false,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:'🍯',fitzpatrick_scale:false,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:'🥐',fitzpatrick_scale:false,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:'🍞',fitzpatrick_scale:false,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:'🥖',fitzpatrick_scale:false,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:'🥯',fitzpatrick_scale:false,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:'🥨',fitzpatrick_scale:false,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:'🧀',fitzpatrick_scale:false,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:'🥚',fitzpatrick_scale:false,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:'🥓',fitzpatrick_scale:false,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:'🥩',fitzpatrick_scale:false,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:'🥞',fitzpatrick_scale:false,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:'🍗',fitzpatrick_scale:false,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:'🍖',fitzpatrick_scale:false,category:"food_and_drink"},bone:{keywords:["skeleton"],char:'🦴',fitzpatrick_scale:false,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:'🍤',fitzpatrick_scale:false,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:'🍳',fitzpatrick_scale:false,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:'🍔',fitzpatrick_scale:false,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:'🍟',fitzpatrick_scale:false,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:'🥙',fitzpatrick_scale:false,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:'🌭',fitzpatrick_scale:false,category:"food_and_drink"},pizza:{keywords:["food","party"],char:'🍕',fitzpatrick_scale:false,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:'🥪',fitzpatrick_scale:false,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:'🥫',fitzpatrick_scale:false,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:'🍝',fitzpatrick_scale:false,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:'🌮',fitzpatrick_scale:false,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:'🌯',fitzpatrick_scale:false,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:'🥗',fitzpatrick_scale:false,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:'🥘',fitzpatrick_scale:false,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:'🍜',fitzpatrick_scale:false,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:'🍲',fitzpatrick_scale:false,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:'🍥',fitzpatrick_scale:false,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:'🥠',fitzpatrick_scale:false,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:'🍣',fitzpatrick_scale:false,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:'🍱',fitzpatrick_scale:false,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:'🍛',fitzpatrick_scale:false,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:'🍙',fitzpatrick_scale:false,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:'🍚',fitzpatrick_scale:false,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:'🍘',fitzpatrick_scale:false,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:'🍢',fitzpatrick_scale:false,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:'🍡',fitzpatrick_scale:false,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:'🍧',fitzpatrick_scale:false,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:'🍨',fitzpatrick_scale:false,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:'🍦',fitzpatrick_scale:false,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:'🥧',fitzpatrick_scale:false,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:'🍰',fitzpatrick_scale:false,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:'🧁',fitzpatrick_scale:false,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:'🥮',fitzpatrick_scale:false,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:'🎂',fitzpatrick_scale:false,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:'🍮',fitzpatrick_scale:false,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:'🍬',fitzpatrick_scale:false,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:'🍭',fitzpatrick_scale:false,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:'🍫',fitzpatrick_scale:false,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:'🍿',fitzpatrick_scale:false,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:'🥟',fitzpatrick_scale:false,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:'🍩',fitzpatrick_scale:false,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:'🍪',fitzpatrick_scale:false,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:'🥛',fitzpatrick_scale:false,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'🍺',fitzpatrick_scale:false,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:'🍻',fitzpatrick_scale:false,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:'🥂',fitzpatrick_scale:false,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:'🍷',fitzpatrick_scale:false,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:'🥃',fitzpatrick_scale:false,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:'🍸',fitzpatrick_scale:false,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:'🍹',fitzpatrick_scale:false,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:'🍾',fitzpatrick_scale:false,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:'🍶',fitzpatrick_scale:false,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:'🍵',fitzpatrick_scale:false,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:'🥤',fitzpatrick_scale:false,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:'☕',fitzpatrick_scale:false,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:'🍼',fitzpatrick_scale:false,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:'🧂',fitzpatrick_scale:false,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:'🥄',fitzpatrick_scale:false,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:'🍴',fitzpatrick_scale:false,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:'🍽',fitzpatrick_scale:false,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:'🥣',fitzpatrick_scale:false,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:'🥡',fitzpatrick_scale:false,category:"food_and_drink"},chopsticks:{keywords:["food"],char:'🥢',fitzpatrick_scale:false,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:'⚽',fitzpatrick_scale:false,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:'🏀',fitzpatrick_scale:false,category:"activity"},football:{keywords:["sports","balls","NFL"],char:'🏈',fitzpatrick_scale:false,category:"activity"},baseball:{keywords:["sports","balls"],char:'⚾',fitzpatrick_scale:false,category:"activity"},softball:{keywords:["sports","balls"],char:'🥎',fitzpatrick_scale:false,category:"activity"},tennis:{keywords:["sports","balls","green"],char:'🎾',fitzpatrick_scale:false,category:"activity"},volleyball:{keywords:["sports","balls"],char:'🏐',fitzpatrick_scale:false,category:"activity"},rugby_football:{keywords:["sports","team"],char:'🏉',fitzpatrick_scale:false,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:'🥏',fitzpatrick_scale:false,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:'🎱',fitzpatrick_scale:false,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:'⛳',fitzpatrick_scale:false,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:'🏌️‍♀️',fitzpatrick_scale:false,category:"activity"},golfing_man:{keywords:["sports","business"],char:'🏌',fitzpatrick_scale:true,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:'🏓',fitzpatrick_scale:false,category:"activity"},badminton:{keywords:["sports"],char:'🏸',fitzpatrick_scale:false,category:"activity"},goal_net:{keywords:["sports"],char:'🥅',fitzpatrick_scale:false,category:"activity"},ice_hockey:{keywords:["sports"],char:'🏒',fitzpatrick_scale:false,category:"activity"},field_hockey:{keywords:["sports"],char:'🏑',fitzpatrick_scale:false,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:'🥍',fitzpatrick_scale:false,category:"activity"},cricket:{keywords:["sports"],char:'🏏',fitzpatrick_scale:false,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:'🎿',fitzpatrick_scale:false,category:"activity"},skier:{keywords:["sports","winter","snow"],char:'⛷',fitzpatrick_scale:false,category:"activity"},snowboarder:{keywords:["sports","winter"],char:'🏂',fitzpatrick_scale:true,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:'🤺',fitzpatrick_scale:false,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:'🤼‍♀️',fitzpatrick_scale:false,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:'🤼‍♂️',fitzpatrick_scale:false,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:'🤸‍♀️',fitzpatrick_scale:true,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:'🤸‍♂️',fitzpatrick_scale:true,category:"activity"},woman_playing_handball:{keywords:["sports"],char:'🤾‍♀️',fitzpatrick_scale:true,category:"activity"},man_playing_handball:{keywords:["sports"],char:'🤾‍♂️',fitzpatrick_scale:true,category:"activity"},ice_skate:{keywords:["sports"],char:'⛸',fitzpatrick_scale:false,category:"activity"},curling_stone:{keywords:["sports"],char:'🥌',fitzpatrick_scale:false,category:"activity"},skateboard:{keywords:["board"],char:'🛹',fitzpatrick_scale:false,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:'🛷',fitzpatrick_scale:false,category:"activity"},bow_and_arrow:{keywords:["sports"],char:'🏹',fitzpatrick_scale:false,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:'🎣',fitzpatrick_scale:false,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:'🥊',fitzpatrick_scale:false,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:'🥋',fitzpatrick_scale:false,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:'🚣‍♀️',fitzpatrick_scale:true,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:'🚣',fitzpatrick_scale:true,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:'🧗‍♀️',fitzpatrick_scale:true,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:'🧗‍♂️',fitzpatrick_scale:true,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:'🏊‍♀️',fitzpatrick_scale:true,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:'🏊',fitzpatrick_scale:true,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:'🤽‍♀️',fitzpatrick_scale:true,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:'🤽‍♂️',fitzpatrick_scale:true,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:'🧘‍♀️',fitzpatrick_scale:true,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:'🧘‍♂️',fitzpatrick_scale:true,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:'🏄‍♀️',fitzpatrick_scale:true,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:'🏄',fitzpatrick_scale:true,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:'🛀',fitzpatrick_scale:true,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:'⛹️‍♀️',fitzpatrick_scale:true,category:"activity"},basketball_man:{keywords:["sports","human"],char:'⛹',fitzpatrick_scale:true,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:'🏋️‍♀️',fitzpatrick_scale:true,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:'🏋',fitzpatrick_scale:true,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:'🚴‍♀️',fitzpatrick_scale:true,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:'🚴',fitzpatrick_scale:true,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:'🚵‍♀️',fitzpatrick_scale:true,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:'🚵',fitzpatrick_scale:true,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:'🏇',fitzpatrick_scale:true,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:'🕴',fitzpatrick_scale:true,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:'🏆',fitzpatrick_scale:false,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:'🎽',fitzpatrick_scale:false,category:"activity"},medal_sports:{keywords:["award","winning"],char:'🏅',fitzpatrick_scale:false,category:"activity"},medal_military:{keywords:["award","winning","army"],char:'🎖',fitzpatrick_scale:false,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:'🥇',fitzpatrick_scale:false,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:'🥈',fitzpatrick_scale:false,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:'🥉',fitzpatrick_scale:false,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:'🎗',fitzpatrick_scale:false,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:'🏵',fitzpatrick_scale:false,category:"activity"},ticket:{keywords:["event","concert","pass"],char:'🎫',fitzpatrick_scale:false,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:'🎟',fitzpatrick_scale:false,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:'🎭',fitzpatrick_scale:false,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:'🎨',fitzpatrick_scale:false,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:'🎪',fitzpatrick_scale:false,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:'🤹‍♀️',fitzpatrick_scale:true,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:'🤹‍♂️',fitzpatrick_scale:true,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:'🎤',fitzpatrick_scale:false,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:'🎧',fitzpatrick_scale:false,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:'🎼',fitzpatrick_scale:false,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:'🎹',fitzpatrick_scale:false,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:'🥁',fitzpatrick_scale:false,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:'🎷',fitzpatrick_scale:false,category:"activity"},trumpet:{keywords:["music","brass"],char:'🎺',fitzpatrick_scale:false,category:"activity"},guitar:{keywords:["music","instrument"],char:'🎸',fitzpatrick_scale:false,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:'🎻',fitzpatrick_scale:false,category:"activity"},clapper:{keywords:["movie","film","record"],char:'🎬',fitzpatrick_scale:false,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:'🎮',fitzpatrick_scale:false,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:'👾',fitzpatrick_scale:false,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:'🎯',fitzpatrick_scale:false,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:'🎲',fitzpatrick_scale:false,category:"activity"},chess_pawn:{keywords:["expendable"],char:"♟",fitzpatrick_scale:false,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:'🎰',fitzpatrick_scale:false,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:'🧩',fitzpatrick_scale:false,category:"activity"},bowling:{keywords:["sports","fun","play"],char:'🎳',fitzpatrick_scale:false,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:'🚗',fitzpatrick_scale:false,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:'🚕',fitzpatrick_scale:false,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:'🚙',fitzpatrick_scale:false,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:'🚌',fitzpatrick_scale:false,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:'🚎',fitzpatrick_scale:false,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:'🏎',fitzpatrick_scale:false,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:'🚓',fitzpatrick_scale:false,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:'🚑',fitzpatrick_scale:false,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:'🚒',fitzpatrick_scale:false,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:'🚐',fitzpatrick_scale:false,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:'🚚',fitzpatrick_scale:false,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:'🚛',fitzpatrick_scale:false,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:'🚜',fitzpatrick_scale:false,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:'🛴',fitzpatrick_scale:false,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:'🏍',fitzpatrick_scale:false,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:'🚲',fitzpatrick_scale:false,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:'🛵',fitzpatrick_scale:false,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:'🚨',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:'🚔',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:'🚍',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:'🚘',fitzpatrick_scale:false,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:'🚖',fitzpatrick_scale:false,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:'🚡',fitzpatrick_scale:false,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:'🚠',fitzpatrick_scale:false,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:'🚟',fitzpatrick_scale:false,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:'🚃',fitzpatrick_scale:false,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:'🚋',fitzpatrick_scale:false,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:'🚝',fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:'🚄',fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:'🚅',fitzpatrick_scale:false,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:'🚈',fitzpatrick_scale:false,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:'🚞',fitzpatrick_scale:false,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:'🚂',fitzpatrick_scale:false,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:'🚆',fitzpatrick_scale:false,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:'🚇',fitzpatrick_scale:false,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:'🚊',fitzpatrick_scale:false,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:'🚉',fitzpatrick_scale:false,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:'🛸',fitzpatrick_scale:false,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:'🚁',fitzpatrick_scale:false,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:'🛩',fitzpatrick_scale:false,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:'✈️',fitzpatrick_scale:false,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:'🛫',fitzpatrick_scale:false,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:'🛬',fitzpatrick_scale:false,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:'⛵',fitzpatrick_scale:false,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:'🛥',fitzpatrick_scale:false,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:'🚤',fitzpatrick_scale:false,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:'⛴',fitzpatrick_scale:false,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:'🛳',fitzpatrick_scale:false,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:'🚀',fitzpatrick_scale:false,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:'🛰',fitzpatrick_scale:false,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:'💺',fitzpatrick_scale:false,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:'🛶',fitzpatrick_scale:false,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:'⚓',fitzpatrick_scale:false,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:'🚧',fitzpatrick_scale:false,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:'⛽',fitzpatrick_scale:false,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:'🚏',fitzpatrick_scale:false,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:'🚦',fitzpatrick_scale:false,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:'🚥',fitzpatrick_scale:false,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:'🏁',fitzpatrick_scale:false,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:'🚢',fitzpatrick_scale:false,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:'🎡',fitzpatrick_scale:false,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:'🎢',fitzpatrick_scale:false,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:'🎠',fitzpatrick_scale:false,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:'🏗',fitzpatrick_scale:false,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:'🌁',fitzpatrick_scale:false,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:'🗼',fitzpatrick_scale:false,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:'🏭',fitzpatrick_scale:false,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:'⛲',fitzpatrick_scale:false,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:'🎑',fitzpatrick_scale:false,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:'⛰',fitzpatrick_scale:false,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:'🏔',fitzpatrick_scale:false,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:'🗻',fitzpatrick_scale:false,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:'🌋',fitzpatrick_scale:false,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:'🗾',fitzpatrick_scale:false,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:'🏕',fitzpatrick_scale:false,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:'⛺',fitzpatrick_scale:false,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:'🏞',fitzpatrick_scale:false,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:'🛣',fitzpatrick_scale:false,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:'🛤',fitzpatrick_scale:false,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:'🌅',fitzpatrick_scale:false,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:'🌄',fitzpatrick_scale:false,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:'🏜',fitzpatrick_scale:false,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:'🏖',fitzpatrick_scale:false,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:'🏝',fitzpatrick_scale:false,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:'🌇',fitzpatrick_scale:false,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:'🌆',fitzpatrick_scale:false,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:'🏙',fitzpatrick_scale:false,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:'🌃',fitzpatrick_scale:false,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:'🌉',fitzpatrick_scale:false,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:'🌌',fitzpatrick_scale:false,category:"travel_and_places"},stars:{keywords:["night","photo"],char:'🌠',fitzpatrick_scale:false,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:'🎇',fitzpatrick_scale:false,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:'🎆',fitzpatrick_scale:false,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:'🌈',fitzpatrick_scale:false,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:'🏘',fitzpatrick_scale:false,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:'🏰',fitzpatrick_scale:false,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:'🏯',fitzpatrick_scale:false,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:'🏟',fitzpatrick_scale:false,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:'🗽',fitzpatrick_scale:false,category:"travel_and_places"},house:{keywords:["building","home"],char:'🏠',fitzpatrick_scale:false,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:'🏡',fitzpatrick_scale:false,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:'🏚',fitzpatrick_scale:false,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:'🏢',fitzpatrick_scale:false,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:'🏬',fitzpatrick_scale:false,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:'🏣',fitzpatrick_scale:false,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:'🏤',fitzpatrick_scale:false,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:'🏥',fitzpatrick_scale:false,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:'🏦',fitzpatrick_scale:false,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:'🏨',fitzpatrick_scale:false,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:'🏪',fitzpatrick_scale:false,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:'🏫',fitzpatrick_scale:false,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:'🏩',fitzpatrick_scale:false,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:'💒',fitzpatrick_scale:false,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:'🏛',fitzpatrick_scale:false,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:'⛪',fitzpatrick_scale:false,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:'🕌',fitzpatrick_scale:false,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:'🕍',fitzpatrick_scale:false,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:'🕋',fitzpatrick_scale:false,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:'⛩',fitzpatrick_scale:false,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:'⌚',fitzpatrick_scale:false,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:'📱',fitzpatrick_scale:false,category:"objects"},calling:{keywords:["iphone","incoming"],char:'📲',fitzpatrick_scale:false,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:'💻',fitzpatrick_scale:false,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:'⌨',fitzpatrick_scale:false,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:'🖥',fitzpatrick_scale:false,category:"objects"},printer:{keywords:["paper","ink"],char:'🖨',fitzpatrick_scale:false,category:"objects"},computer_mouse:{keywords:["click"],char:'🖱',fitzpatrick_scale:false,category:"objects"},trackball:{keywords:["technology","trackpad"],char:'🖲',fitzpatrick_scale:false,category:"objects"},joystick:{keywords:["game","play"],char:'🕹',fitzpatrick_scale:false,category:"objects"},clamp:{keywords:["tool"],char:'🗜',fitzpatrick_scale:false,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:'💽',fitzpatrick_scale:false,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:'💾',fitzpatrick_scale:false,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:'💿',fitzpatrick_scale:false,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:'📀',fitzpatrick_scale:false,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:'📼',fitzpatrick_scale:false,category:"objects"},camera:{keywords:["gadgets","photography"],char:'📷',fitzpatrick_scale:false,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:'📸',fitzpatrick_scale:false,category:"objects"},video_camera:{keywords:["film","record"],char:'📹',fitzpatrick_scale:false,category:"objects"},movie_camera:{keywords:["film","record"],char:'🎥',fitzpatrick_scale:false,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:'📽',fitzpatrick_scale:false,category:"objects"},film_strip:{keywords:["movie"],char:'🎞',fitzpatrick_scale:false,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:'📞',fitzpatrick_scale:false,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:'☎️',fitzpatrick_scale:false,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:'📟',fitzpatrick_scale:false,category:"objects"},fax:{keywords:["communication","technology"],char:'📠',fitzpatrick_scale:false,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:'📺',fitzpatrick_scale:false,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:'📻',fitzpatrick_scale:false,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:'🎙',fitzpatrick_scale:false,category:"objects"},level_slider:{keywords:["scale"],char:'🎚',fitzpatrick_scale:false,category:"objects"},control_knobs:{keywords:["dial"],char:'🎛',fitzpatrick_scale:false,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:'🧭',fitzpatrick_scale:false,category:"objects"},stopwatch:{keywords:["time","deadline"],char:'⏱',fitzpatrick_scale:false,category:"objects"},timer_clock:{keywords:["alarm"],char:'⏲',fitzpatrick_scale:false,category:"objects"},alarm_clock:{keywords:["time","wake"],char:'⏰',fitzpatrick_scale:false,category:"objects"},mantelpiece_clock:{keywords:["time"],char:'🕰',fitzpatrick_scale:false,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:'⏳',fitzpatrick_scale:false,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:'⌛',fitzpatrick_scale:false,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:'📡',fitzpatrick_scale:false,category:"objects"},battery:{keywords:["power","energy","sustain"],char:'🔋',fitzpatrick_scale:false,category:"objects"},electric_plug:{keywords:["charger","power"],char:'🔌',fitzpatrick_scale:false,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:'💡',fitzpatrick_scale:false,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:'🔦',fitzpatrick_scale:false,category:"objects"},candle:{keywords:["fire","wax"],char:'🕯',fitzpatrick_scale:false,category:"objects"},fire_extinguisher:{keywords:["quench"],char:'🧯',fitzpatrick_scale:false,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:'🗑',fitzpatrick_scale:false,category:"objects"},oil_drum:{keywords:["barrell"],char:'🛢',fitzpatrick_scale:false,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:'💸',fitzpatrick_scale:false,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:'💵',fitzpatrick_scale:false,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:'💴',fitzpatrick_scale:false,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:'💶',fitzpatrick_scale:false,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:'💷',fitzpatrick_scale:false,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:'💰',fitzpatrick_scale:false,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:'💳',fitzpatrick_scale:false,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:'💎',fitzpatrick_scale:false,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:'⚖',fitzpatrick_scale:false,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:'🧰',fitzpatrick_scale:false,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:'🔧',fitzpatrick_scale:false,category:"objects"},hammer:{keywords:["tools","build","create"],char:'🔨',fitzpatrick_scale:false,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:'⚒',fitzpatrick_scale:false,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:'🛠',fitzpatrick_scale:false,category:"objects"},pick:{keywords:["tools","dig"],char:'⛏',fitzpatrick_scale:false,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:'🔩',fitzpatrick_scale:false,category:"objects"},gear:{keywords:["cog"],char:'⚙',fitzpatrick_scale:false,category:"objects"},brick:{keywords:["bricks"],char:'🧱',fitzpatrick_scale:false,category:"objects"},chains:{keywords:["lock","arrest"],char:'⛓',fitzpatrick_scale:false,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:'🧲',fitzpatrick_scale:false,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:'🔫',fitzpatrick_scale:false,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:'💣',fitzpatrick_scale:false,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:'🧨',fitzpatrick_scale:false,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:'🔪',fitzpatrick_scale:false,category:"objects"},dagger:{keywords:["weapon"],char:'🗡',fitzpatrick_scale:false,category:"objects"},crossed_swords:{keywords:["weapon"],char:'⚔',fitzpatrick_scale:false,category:"objects"},shield:{keywords:["protection","security"],char:'🛡',fitzpatrick_scale:false,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:'🚬',fitzpatrick_scale:false,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:'☠',fitzpatrick_scale:false,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:'⚰',fitzpatrick_scale:false,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:'⚱',fitzpatrick_scale:false,category:"objects"},amphora:{keywords:["vase","jar"],char:'🏺',fitzpatrick_scale:false,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:'🔮',fitzpatrick_scale:false,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:'📿',fitzpatrick_scale:false,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:'🧿',fitzpatrick_scale:false,category:"objects"},barber:{keywords:["hair","salon","style"],char:'💈',fitzpatrick_scale:false,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:'⚗',fitzpatrick_scale:false,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:'🔭',fitzpatrick_scale:false,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:'🔬',fitzpatrick_scale:false,category:"objects"},hole:{keywords:["embarrassing"],char:'🕳',fitzpatrick_scale:false,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:'💊',fitzpatrick_scale:false,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:'💉',fitzpatrick_scale:false,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:'🧬',fitzpatrick_scale:false,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:'🦠',fitzpatrick_scale:false,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:'🧫',fitzpatrick_scale:false,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:'🧪',fitzpatrick_scale:false,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:'🌡',fitzpatrick_scale:false,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:'🧹',fitzpatrick_scale:false,category:"objects"},basket:{keywords:["laundry"],char:'🧺',fitzpatrick_scale:false,category:"objects"},toilet_paper:{keywords:["roll"],char:'🧻',fitzpatrick_scale:false,category:"objects"},label:{keywords:["sale","tag"],char:'🏷',fitzpatrick_scale:false,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:'🔖',fitzpatrick_scale:false,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:'🚽',fitzpatrick_scale:false,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:'🚿',fitzpatrick_scale:false,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:'🛁',fitzpatrick_scale:false,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:'🧼',fitzpatrick_scale:false,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:'🧽',fitzpatrick_scale:false,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:'🧴',fitzpatrick_scale:false,category:"objects"},key:{keywords:["lock","door","password"],char:'🔑',fitzpatrick_scale:false,category:"objects"},old_key:{keywords:["lock","door","password"],char:'🗝',fitzpatrick_scale:false,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:'🛋',fitzpatrick_scale:false,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:'🛌',fitzpatrick_scale:true,category:"objects"},bed:{keywords:["sleep","rest"],char:'🛏',fitzpatrick_scale:false,category:"objects"},door:{keywords:["house","entry","exit"],char:'🚪',fitzpatrick_scale:false,category:"objects"},bellhop_bell:{keywords:["service"],char:'🛎',fitzpatrick_scale:false,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:'🧸',fitzpatrick_scale:false,category:"objects"},framed_picture:{keywords:["photography"],char:'🖼',fitzpatrick_scale:false,category:"objects"},world_map:{keywords:["location","direction"],char:'🗺',fitzpatrick_scale:false,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:'⛱',fitzpatrick_scale:false,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:'🗿',fitzpatrick_scale:false,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:'🛍',fitzpatrick_scale:false,category:"objects"},shopping_cart:{keywords:["trolley"],char:'🛒',fitzpatrick_scale:false,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:'🎈',fitzpatrick_scale:false,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:'🎏',fitzpatrick_scale:false,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:'🎀',fitzpatrick_scale:false,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:'🎁',fitzpatrick_scale:false,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:'🎊',fitzpatrick_scale:false,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:'🎉',fitzpatrick_scale:false,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:'🎎',fitzpatrick_scale:false,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:'🎐',fitzpatrick_scale:false,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:'🎌',fitzpatrick_scale:false,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:'🏮',fitzpatrick_scale:false,category:"objects"},red_envelope:{keywords:["gift"],char:'🧧',fitzpatrick_scale:false,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:'✉️',fitzpatrick_scale:false,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:'📩',fitzpatrick_scale:false,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:'📨',fitzpatrick_scale:false,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:'📧',fitzpatrick_scale:false,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:'💌',fitzpatrick_scale:false,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:'📮',fitzpatrick_scale:false,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:'📪',fitzpatrick_scale:false,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:'📫',fitzpatrick_scale:false,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:'📬',fitzpatrick_scale:false,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:'📭',fitzpatrick_scale:false,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:'📦',fitzpatrick_scale:false,category:"objects"},postal_horn:{keywords:["instrument","music"],char:'📯',fitzpatrick_scale:false,category:"objects"},inbox_tray:{keywords:["email","documents"],char:'📥',fitzpatrick_scale:false,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:'📤',fitzpatrick_scale:false,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:'📜',fitzpatrick_scale:false,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:'📃',fitzpatrick_scale:false,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:'📑',fitzpatrick_scale:false,category:"objects"},receipt:{keywords:["accounting","expenses"],char:'🧾',fitzpatrick_scale:false,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:'📊',fitzpatrick_scale:false,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:'📈',fitzpatrick_scale:false,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:'📉',fitzpatrick_scale:false,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:'📄',fitzpatrick_scale:false,category:"objects"},date:{keywords:["calendar","schedule"],char:'📅',fitzpatrick_scale:false,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:'📆',fitzpatrick_scale:false,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:'🗓',fitzpatrick_scale:false,category:"objects"},card_index:{keywords:["business","stationery"],char:'📇',fitzpatrick_scale:false,category:"objects"},card_file_box:{keywords:["business","stationery"],char:'🗃',fitzpatrick_scale:false,category:"objects"},ballot_box:{keywords:["election","vote"],char:'🗳',fitzpatrick_scale:false,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:'🗄',fitzpatrick_scale:false,category:"objects"},clipboard:{keywords:["stationery","documents"],char:'📋',fitzpatrick_scale:false,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:'🗒',fitzpatrick_scale:false,category:"objects"},file_folder:{keywords:["documents","business","office"],char:'📁',fitzpatrick_scale:false,category:"objects"},open_file_folder:{keywords:["documents","load"],char:'📂',fitzpatrick_scale:false,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:'🗂',fitzpatrick_scale:false,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:'🗞',fitzpatrick_scale:false,category:"objects"},newspaper:{keywords:["press","headline"],char:'📰',fitzpatrick_scale:false,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:'📓',fitzpatrick_scale:false,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:'📕',fitzpatrick_scale:false,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:'📗',fitzpatrick_scale:false,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:'📘',fitzpatrick_scale:false,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:'📙',fitzpatrick_scale:false,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:'📔',fitzpatrick_scale:false,category:"objects"},ledger:{keywords:["notes","paper"],char:'📒',fitzpatrick_scale:false,category:"objects"},books:{keywords:["literature","library","study"],char:'📚',fitzpatrick_scale:false,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:'📖',fitzpatrick_scale:false,category:"objects"},safety_pin:{keywords:["diaper"],char:'🧷',fitzpatrick_scale:false,category:"objects"},link:{keywords:["rings","url"],char:'🔗',fitzpatrick_scale:false,category:"objects"},paperclip:{keywords:["documents","stationery"],char:'📎',fitzpatrick_scale:false,category:"objects"},paperclips:{keywords:["documents","stationery"],char:'🖇',fitzpatrick_scale:false,category:"objects"},scissors:{keywords:["stationery","cut"],char:'✂️',fitzpatrick_scale:false,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:'📐',fitzpatrick_scale:false,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:'📏',fitzpatrick_scale:false,category:"objects"},abacus:{keywords:["calculation"],char:'🧮',fitzpatrick_scale:false,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:'📌',fitzpatrick_scale:false,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:'📍',fitzpatrick_scale:false,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:'🚩',fitzpatrick_scale:false,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:'🏳',fitzpatrick_scale:false,category:"objects"},black_flag:{keywords:["pirate"],char:'🏴',fitzpatrick_scale:false,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:'🏳️‍🌈',fitzpatrick_scale:false,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:'🔐',fitzpatrick_scale:false,category:"objects"},lock:{keywords:["security","password","padlock"],char:'🔒',fitzpatrick_scale:false,category:"objects"},unlock:{keywords:["privacy","security"],char:'🔓',fitzpatrick_scale:false,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:'🔏',fitzpatrick_scale:false,category:"objects"},pen:{keywords:["stationery","writing","write"],char:'🖊',fitzpatrick_scale:false,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:'🖋',fitzpatrick_scale:false,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:'✒️',fitzpatrick_scale:false,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:'📝',fitzpatrick_scale:false,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:'✏️',fitzpatrick_scale:false,category:"objects"},crayon:{keywords:["drawing","creativity"],char:'🖍',fitzpatrick_scale:false,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:'🖌',fitzpatrick_scale:false,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:'🔍',fitzpatrick_scale:false,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:'🔎',fitzpatrick_scale:false,category:"objects"},heart:{keywords:["love","like","valentines"],char:'❤️',fitzpatrick_scale:false,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:'🧡',fitzpatrick_scale:false,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:'💛',fitzpatrick_scale:false,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:'💚',fitzpatrick_scale:false,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:'💙',fitzpatrick_scale:false,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:'💜',fitzpatrick_scale:false,category:"symbols"},black_heart:{keywords:["evil"],char:'🖤',fitzpatrick_scale:false,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:'💔',fitzpatrick_scale:false,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:'❣',fitzpatrick_scale:false,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:'💕',fitzpatrick_scale:false,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:'💞',fitzpatrick_scale:false,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:'💓',fitzpatrick_scale:false,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:'💗',fitzpatrick_scale:false,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:'💖',fitzpatrick_scale:false,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:'💘',fitzpatrick_scale:false,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:'💝',fitzpatrick_scale:false,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:'💟',fitzpatrick_scale:false,category:"symbols"},peace_symbol:{keywords:["hippie"],char:'☮',fitzpatrick_scale:false,category:"symbols"},latin_cross:{keywords:["christianity"],char:'✝',fitzpatrick_scale:false,category:"symbols"},star_and_crescent:{keywords:["islam"],char:'☪',fitzpatrick_scale:false,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'🕉',fitzpatrick_scale:false,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:'☸',fitzpatrick_scale:false,category:"symbols"},star_of_david:{keywords:["judaism"],char:'✡',fitzpatrick_scale:false,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:'🔯',fitzpatrick_scale:false,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:'🕎',fitzpatrick_scale:false,category:"symbols"},yin_yang:{keywords:["balance"],char:'☯',fitzpatrick_scale:false,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:'☦',fitzpatrick_scale:false,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:'🛐',fitzpatrick_scale:false,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:'⛎',fitzpatrick_scale:false,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:'♈',fitzpatrick_scale:false,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:'♉',fitzpatrick_scale:false,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:'♊',fitzpatrick_scale:false,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:'♋',fitzpatrick_scale:false,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:'♌',fitzpatrick_scale:false,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:'♍',fitzpatrick_scale:false,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:'♎',fitzpatrick_scale:false,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:'♏',fitzpatrick_scale:false,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:'♐',fitzpatrick_scale:false,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:'♑',fitzpatrick_scale:false,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:'♒',fitzpatrick_scale:false,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:'♓',fitzpatrick_scale:false,category:"symbols"},id:{keywords:["purple-square","words"],char:'🆔',fitzpatrick_scale:false,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:'⚛',fitzpatrick_scale:false,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:'🈳',fitzpatrick_scale:false,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:'🈹',fitzpatrick_scale:false,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:'☢',fitzpatrick_scale:false,category:"symbols"},biohazard:{keywords:["danger"],char:'☣',fitzpatrick_scale:false,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:'📴',fitzpatrick_scale:false,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:'📳',fitzpatrick_scale:false,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:'🈶',fitzpatrick_scale:false,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:'🈚',fitzpatrick_scale:false,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:'🈸',fitzpatrick_scale:false,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:'🈺',fitzpatrick_scale:false,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:'🈷️',fitzpatrick_scale:false,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:'✴️',fitzpatrick_scale:false,category:"symbols"},vs:{keywords:["words","orange-square"],char:'🆚',fitzpatrick_scale:false,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:'🉑',fitzpatrick_scale:false,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:'💮',fitzpatrick_scale:false,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:'🉐',fitzpatrick_scale:false,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:'㊙️',fitzpatrick_scale:false,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:'㊗️',fitzpatrick_scale:false,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:'🈴',fitzpatrick_scale:false,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:'🈵',fitzpatrick_scale:false,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:'🈲',fitzpatrick_scale:false,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:'🅰️',fitzpatrick_scale:false,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:'🅱️',fitzpatrick_scale:false,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:'🆎',fitzpatrick_scale:false,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:'🆑',fitzpatrick_scale:false,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:'🅾️',fitzpatrick_scale:false,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:'🆘',fitzpatrick_scale:false,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:'⛔',fitzpatrick_scale:false,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:'📛',fitzpatrick_scale:false,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:'🚫',fitzpatrick_scale:false,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:'❌',fitzpatrick_scale:false,category:"symbols"},o:{keywords:["circle","round"],char:'⭕',fitzpatrick_scale:false,category:"symbols"},stop_sign:{keywords:["stop"],char:'🛑',fitzpatrick_scale:false,category:"symbols"},anger:{keywords:["angry","mad"],char:'💢',fitzpatrick_scale:false,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:'♨️',fitzpatrick_scale:false,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:'🚷',fitzpatrick_scale:false,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:'🚯',fitzpatrick_scale:false,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:'🚳',fitzpatrick_scale:false,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:'🚱',fitzpatrick_scale:false,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:'🔞',fitzpatrick_scale:false,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:'📵',fitzpatrick_scale:false,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:'❗',fitzpatrick_scale:false,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:'❕',fitzpatrick_scale:false,category:"symbols"},question:{keywords:["doubt","confused"],char:'❓',fitzpatrick_scale:false,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:'❔',fitzpatrick_scale:false,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:'‼️',fitzpatrick_scale:false,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:'⁉️',fitzpatrick_scale:false,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:'🔅',fitzpatrick_scale:false,category:"symbols"},high_brightness:{keywords:["sun","light"],char:'🔆',fitzpatrick_scale:false,category:"symbols"},trident:{keywords:["weapon","spear"],char:'🔱',fitzpatrick_scale:false,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:'⚜',fitzpatrick_scale:false,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:'〽️',fitzpatrick_scale:false,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:'⚠️',fitzpatrick_scale:false,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:'🚸',fitzpatrick_scale:false,category:"symbols"},beginner:{keywords:["badge","shield"],char:'🔰',fitzpatrick_scale:false,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:'♻️',fitzpatrick_scale:false,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:'🈯',fitzpatrick_scale:false,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:'💹',fitzpatrick_scale:false,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:'❇️',fitzpatrick_scale:false,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:'✳️',fitzpatrick_scale:false,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:'❎',fitzpatrick_scale:false,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:'✅',fitzpatrick_scale:false,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:'💠',fitzpatrick_scale:false,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:'🌀',fitzpatrick_scale:false,category:"symbols"},loop:{keywords:["tape","cassette"],char:'➿',fitzpatrick_scale:false,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:'🌐',fitzpatrick_scale:false,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:'Ⓜ️',fitzpatrick_scale:false,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:'🏧',fitzpatrick_scale:false,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:'🈂️',fitzpatrick_scale:false,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:'🛂',fitzpatrick_scale:false,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:'🛃',fitzpatrick_scale:false,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:'🛄',fitzpatrick_scale:false,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:'🛅',fitzpatrick_scale:false,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:'♿',fitzpatrick_scale:false,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:'🚭',fitzpatrick_scale:false,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:'🚾',fitzpatrick_scale:false,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:'🅿️',fitzpatrick_scale:false,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:'🚰',fitzpatrick_scale:false,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:'🚹',fitzpatrick_scale:false,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:'🚺',fitzpatrick_scale:false,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:'🚼',fitzpatrick_scale:false,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:'🚻',fitzpatrick_scale:false,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:'🚮',fitzpatrick_scale:false,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:'🎦',fitzpatrick_scale:false,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:'📶',fitzpatrick_scale:false,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:'🈁',fitzpatrick_scale:false,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:'🆖',fitzpatrick_scale:false,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:'🆗',fitzpatrick_scale:false,category:"symbols"},up:{keywords:["blue-square","above","high"],char:'🆙',fitzpatrick_scale:false,category:"symbols"},cool:{keywords:["words","blue-square"],char:'🆒',fitzpatrick_scale:false,category:"symbols"},new:{keywords:["blue-square","words","start"],char:'🆕',fitzpatrick_scale:false,category:"symbols"},free:{keywords:["blue-square","words"],char:'🆓',fitzpatrick_scale:false,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:'0️⃣',fitzpatrick_scale:false,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:'1️⃣',fitzpatrick_scale:false,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:'2️⃣',fitzpatrick_scale:false,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:'3️⃣',fitzpatrick_scale:false,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:'4️⃣',fitzpatrick_scale:false,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:'5️⃣',fitzpatrick_scale:false,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:'6️⃣',fitzpatrick_scale:false,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:'7️⃣',fitzpatrick_scale:false,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:'8️⃣',fitzpatrick_scale:false,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:'9️⃣',fitzpatrick_scale:false,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:'🔟',fitzpatrick_scale:false,category:"symbols"},asterisk:{keywords:["star","keycap"],char:'*⃣',fitzpatrick_scale:false,category:"symbols"},eject_button:{keywords:["blue-square"],char:'⏏️',fitzpatrick_scale:false,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:'▶️',fitzpatrick_scale:false,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:'⏸',fitzpatrick_scale:false,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:'⏭',fitzpatrick_scale:false,category:"symbols"},stop_button:{keywords:["blue-square"],char:'⏹',fitzpatrick_scale:false,category:"symbols"},record_button:{keywords:["blue-square"],char:'⏺',fitzpatrick_scale:false,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:'⏯',fitzpatrick_scale:false,category:"symbols"},previous_track_button:{keywords:["backward"],char:'⏮',fitzpatrick_scale:false,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:'⏩',fitzpatrick_scale:false,category:"symbols"},rewind:{keywords:["play","blue-square"],char:'⏪',fitzpatrick_scale:false,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:'🔀',fitzpatrick_scale:false,category:"symbols"},repeat:{keywords:["loop","record"],char:'🔁',fitzpatrick_scale:false,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:'🔂',fitzpatrick_scale:false,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:'◀️',fitzpatrick_scale:false,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:'🔼',fitzpatrick_scale:false,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:'🔽',fitzpatrick_scale:false,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:'⏫',fitzpatrick_scale:false,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:'⏬',fitzpatrick_scale:false,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:'➡️',fitzpatrick_scale:false,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:'⬅️',fitzpatrick_scale:false,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:'⬆️',fitzpatrick_scale:false,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:'⬇️',fitzpatrick_scale:false,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:'↗️',fitzpatrick_scale:false,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:'↘️',fitzpatrick_scale:false,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:'↙️',fitzpatrick_scale:false,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:'↖️',fitzpatrick_scale:false,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:'↕️',fitzpatrick_scale:false,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:'↔️',fitzpatrick_scale:false,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:'🔄',fitzpatrick_scale:false,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:'↪️',fitzpatrick_scale:false,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:'↩️',fitzpatrick_scale:false,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:'⤴️',fitzpatrick_scale:false,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:'⤵️',fitzpatrick_scale:false,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:'#️⃣',fitzpatrick_scale:false,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:'ℹ️',fitzpatrick_scale:false,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:'🔤',fitzpatrick_scale:false,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:'🔡',fitzpatrick_scale:false,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:'🔠',fitzpatrick_scale:false,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:'🔣',fitzpatrick_scale:false,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:'🎵',fitzpatrick_scale:false,category:"symbols"},notes:{keywords:["music","score"],char:'🎶',fitzpatrick_scale:false,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:'〰️',fitzpatrick_scale:false,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:'➰',fitzpatrick_scale:false,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:'✔️',fitzpatrick_scale:false,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:'🔃',fitzpatrick_scale:false,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:'➕',fitzpatrick_scale:false,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:'➖',fitzpatrick_scale:false,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:'➗',fitzpatrick_scale:false,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:'✖️',fitzpatrick_scale:false,category:"symbols"},infinity:{keywords:["forever"],char:'♾',fitzpatrick_scale:false,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:'💲',fitzpatrick_scale:false,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:'💱',fitzpatrick_scale:false,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:'©️',fitzpatrick_scale:false,category:"symbols"},registered:{keywords:["alphabet","circle"],char:'®️',fitzpatrick_scale:false,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:'™️',fitzpatrick_scale:false,category:"symbols"},end:{keywords:["words","arrow"],char:'🔚',fitzpatrick_scale:false,category:"symbols"},back:{keywords:["arrow","words","return"],char:'🔙',fitzpatrick_scale:false,category:"symbols"},on:{keywords:["arrow","words"],char:'🔛',fitzpatrick_scale:false,category:"symbols"},top:{keywords:["words","blue-square"],char:'🔝',fitzpatrick_scale:false,category:"symbols"},soon:{keywords:["arrow","words"],char:'🔜',fitzpatrick_scale:false,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:'☑️',fitzpatrick_scale:false,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:'🔘',fitzpatrick_scale:false,category:"symbols"},white_circle:{keywords:["shape","round"],char:'⚪',fitzpatrick_scale:false,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:'⚫',fitzpatrick_scale:false,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:'🔴',fitzpatrick_scale:false,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:'🔵',fitzpatrick_scale:false,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:'🔸',fitzpatrick_scale:false,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:'🔹',fitzpatrick_scale:false,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:'🔶',fitzpatrick_scale:false,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:'🔷',fitzpatrick_scale:false,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:'🔺',fitzpatrick_scale:false,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:'▪️',fitzpatrick_scale:false,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:'▫️',fitzpatrick_scale:false,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:'⬛',fitzpatrick_scale:false,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:'⬜',fitzpatrick_scale:false,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:'🔻',fitzpatrick_scale:false,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:'◼️',fitzpatrick_scale:false,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:'◻️',fitzpatrick_scale:false,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:'◾',fitzpatrick_scale:false,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:'◽',fitzpatrick_scale:false,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:'🔲',fitzpatrick_scale:false,category:"symbols"},white_square_button:{keywords:["shape","input"],char:'🔳',fitzpatrick_scale:false,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:'🔈',fitzpatrick_scale:false,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:'🔉',fitzpatrick_scale:false,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:'🔊',fitzpatrick_scale:false,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:'🔇',fitzpatrick_scale:false,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:'📣',fitzpatrick_scale:false,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:'📢',fitzpatrick_scale:false,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:'🔔',fitzpatrick_scale:false,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:'🔕',fitzpatrick_scale:false,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:'🃏',fitzpatrick_scale:false,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:'🀄',fitzpatrick_scale:false,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:'♠️',fitzpatrick_scale:false,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:'♣️',fitzpatrick_scale:false,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:'♥️',fitzpatrick_scale:false,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:'♦️',fitzpatrick_scale:false,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:'🎴',fitzpatrick_scale:false,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:'💭',fitzpatrick_scale:false,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:'🗯',fitzpatrick_scale:false,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:'💬',fitzpatrick_scale:false,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:'🗨',fitzpatrick_scale:false,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:'🕐',fitzpatrick_scale:false,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:'🕑',fitzpatrick_scale:false,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:'🕒',fitzpatrick_scale:false,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:'🕓',fitzpatrick_scale:false,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:'🕔',fitzpatrick_scale:false,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:'🕕',fitzpatrick_scale:false,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:'🕖',fitzpatrick_scale:false,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:'🕗',fitzpatrick_scale:false,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:'🕘',fitzpatrick_scale:false,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:'🕙',fitzpatrick_scale:false,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:'🕚',fitzpatrick_scale:false,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:'🕛',fitzpatrick_scale:false,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:'🕜',fitzpatrick_scale:false,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:'🕝',fitzpatrick_scale:false,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:'🕞',fitzpatrick_scale:false,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:'🕟',fitzpatrick_scale:false,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:'🕠',fitzpatrick_scale:false,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:'🕡',fitzpatrick_scale:false,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:'🕢',fitzpatrick_scale:false,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:'🕣',fitzpatrick_scale:false,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:'🕤',fitzpatrick_scale:false,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:'🕥',fitzpatrick_scale:false,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:'🕦',fitzpatrick_scale:false,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:'🕧',fitzpatrick_scale:false,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:'🇦🇫',fitzpatrick_scale:false,category:"flags"},aland_islands:{keywords:["Åland","islands","flag","nation","country","banner"],char:'🇦🇽',fitzpatrick_scale:false,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:'🇦🇱',fitzpatrick_scale:false,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:'🇩🇿',fitzpatrick_scale:false,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:'🇦🇸',fitzpatrick_scale:false,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:'🇦🇩',fitzpatrick_scale:false,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:'🇦🇴',fitzpatrick_scale:false,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:'🇦🇮',fitzpatrick_scale:false,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:'🇦🇶',fitzpatrick_scale:false,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:'🇦🇬',fitzpatrick_scale:false,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:'🇦🇷',fitzpatrick_scale:false,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:'🇦🇲',fitzpatrick_scale:false,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:'🇦🇼',fitzpatrick_scale:false,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:'🇦🇺',fitzpatrick_scale:false,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:'🇦🇹',fitzpatrick_scale:false,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:'🇦🇿',fitzpatrick_scale:false,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:'🇧🇸',fitzpatrick_scale:false,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:'🇧🇭',fitzpatrick_scale:false,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:'🇧🇩',fitzpatrick_scale:false,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:'🇧🇧',fitzpatrick_scale:false,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:'🇧🇾',fitzpatrick_scale:false,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:'🇧🇪',fitzpatrick_scale:false,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:'🇧🇿',fitzpatrick_scale:false,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:'🇧🇯',fitzpatrick_scale:false,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:'🇧🇲',fitzpatrick_scale:false,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:'🇧🇹',fitzpatrick_scale:false,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:'🇧🇴',fitzpatrick_scale:false,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:'🇧🇶',fitzpatrick_scale:false,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:'🇧🇦',fitzpatrick_scale:false,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:'🇧🇼',fitzpatrick_scale:false,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:'🇧🇷',fitzpatrick_scale:false,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:'🇮🇴',fitzpatrick_scale:false,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:'🇻🇬',fitzpatrick_scale:false,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:'🇧🇳',fitzpatrick_scale:false,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:'🇧🇬',fitzpatrick_scale:false,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:'🇧🇫',fitzpatrick_scale:false,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:'🇧🇮',fitzpatrick_scale:false,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:'🇨🇻',fitzpatrick_scale:false,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:'🇰🇭',fitzpatrick_scale:false,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:'🇨🇲',fitzpatrick_scale:false,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:'🇨🇦',fitzpatrick_scale:false,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:'🇮🇨',fitzpatrick_scale:false,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:'🇰🇾',fitzpatrick_scale:false,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:'🇨🇫',fitzpatrick_scale:false,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:'🇹🇩',fitzpatrick_scale:false,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:'🇨🇱',fitzpatrick_scale:false,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:'🇨🇳',fitzpatrick_scale:false,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:'🇨🇽',fitzpatrick_scale:false,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:'🇨🇨',fitzpatrick_scale:false,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:'🇨🇴',fitzpatrick_scale:false,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:'🇰🇲',fitzpatrick_scale:false,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:'🇨🇬',fitzpatrick_scale:false,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:'🇨🇩',fitzpatrick_scale:false,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:'🇨🇰',fitzpatrick_scale:false,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:'🇨🇷',fitzpatrick_scale:false,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:'🇭🇷',fitzpatrick_scale:false,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:'🇨🇺',fitzpatrick_scale:false,category:"flags"},curacao:{keywords:["curaçao","flag","nation","country","banner"],char:'🇨🇼',fitzpatrick_scale:false,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:'🇨🇾',fitzpatrick_scale:false,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:'🇨🇿',fitzpatrick_scale:false,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:'🇩🇰',fitzpatrick_scale:false,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:'🇩🇯',fitzpatrick_scale:false,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:'🇩🇲',fitzpatrick_scale:false,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:'🇩🇴',fitzpatrick_scale:false,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:'🇪🇨',fitzpatrick_scale:false,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:'🇪🇬',fitzpatrick_scale:false,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:'🇸🇻',fitzpatrick_scale:false,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:'🇬🇶',fitzpatrick_scale:false,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:'🇪🇷',fitzpatrick_scale:false,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:'🇪🇪',fitzpatrick_scale:false,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:'🇪🇹',fitzpatrick_scale:false,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:'🇪🇺',fitzpatrick_scale:false,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:'🇫🇰',fitzpatrick_scale:false,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:'🇫🇴',fitzpatrick_scale:false,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:'🇫🇯',fitzpatrick_scale:false,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:'🇫🇮',fitzpatrick_scale:false,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:'🇫🇷',fitzpatrick_scale:false,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:'🇬🇫',fitzpatrick_scale:false,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:'🇵🇫',fitzpatrick_scale:false,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:'🇹🇫',fitzpatrick_scale:false,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:'🇬🇦',fitzpatrick_scale:false,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:'🇬🇲',fitzpatrick_scale:false,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:'🇬🇪',fitzpatrick_scale:false,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:'🇩🇪',fitzpatrick_scale:false,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:'🇬🇭',fitzpatrick_scale:false,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:'🇬🇮',fitzpatrick_scale:false,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:'🇬🇷',fitzpatrick_scale:false,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:'🇬🇱',fitzpatrick_scale:false,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:'🇬🇩',fitzpatrick_scale:false,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:'🇬🇵',fitzpatrick_scale:false,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:'🇬🇺',fitzpatrick_scale:false,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:'🇬🇹',fitzpatrick_scale:false,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:'🇬🇬',fitzpatrick_scale:false,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:'🇬🇳',fitzpatrick_scale:false,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:'🇬🇼',fitzpatrick_scale:false,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:'🇬🇾',fitzpatrick_scale:false,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:'🇭🇹',fitzpatrick_scale:false,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:'🇭🇳',fitzpatrick_scale:false,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:'🇭🇰',fitzpatrick_scale:false,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:'🇭🇺',fitzpatrick_scale:false,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:'🇮🇸',fitzpatrick_scale:false,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:'🇮🇳',fitzpatrick_scale:false,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:'🇮🇩',fitzpatrick_scale:false,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:'🇮🇷',fitzpatrick_scale:false,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:'🇮🇶',fitzpatrick_scale:false,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:'🇮🇪',fitzpatrick_scale:false,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:'🇮🇲',fitzpatrick_scale:false,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:'🇮🇱',fitzpatrick_scale:false,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:'🇮🇹',fitzpatrick_scale:false,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:'🇨🇮',fitzpatrick_scale:false,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:'🇯🇲',fitzpatrick_scale:false,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:'🇯🇵',fitzpatrick_scale:false,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:'🇯🇪',fitzpatrick_scale:false,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:'🇯🇴',fitzpatrick_scale:false,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:'🇰🇿',fitzpatrick_scale:false,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:'🇰🇪',fitzpatrick_scale:false,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:'🇰🇮',fitzpatrick_scale:false,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:'🇽🇰',fitzpatrick_scale:false,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:'🇰🇼',fitzpatrick_scale:false,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:'🇰🇬',fitzpatrick_scale:false,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:'🇱🇦',fitzpatrick_scale:false,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:'🇱🇻',fitzpatrick_scale:false,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:'🇱🇧',fitzpatrick_scale:false,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:'🇱🇸',fitzpatrick_scale:false,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:'🇱🇷',fitzpatrick_scale:false,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:'🇱🇾',fitzpatrick_scale:false,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:'🇱🇮',fitzpatrick_scale:false,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:'🇱🇹',fitzpatrick_scale:false,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:'🇱🇺',fitzpatrick_scale:false,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:'🇲🇴',fitzpatrick_scale:false,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:'🇲🇰',fitzpatrick_scale:false,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:'🇲🇬',fitzpatrick_scale:false,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:'🇲🇼',fitzpatrick_scale:false,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:'🇲🇾',fitzpatrick_scale:false,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:'🇲🇻',fitzpatrick_scale:false,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:'🇲🇱',fitzpatrick_scale:false,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:'🇲🇹',fitzpatrick_scale:false,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:'🇲🇭',fitzpatrick_scale:false,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:'🇲🇶',fitzpatrick_scale:false,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:'🇲🇷',fitzpatrick_scale:false,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:'🇲🇺',fitzpatrick_scale:false,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:'🇾🇹',fitzpatrick_scale:false,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:'🇲🇽',fitzpatrick_scale:false,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:'🇫🇲',fitzpatrick_scale:false,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:'🇲🇩',fitzpatrick_scale:false,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:'🇲🇨',fitzpatrick_scale:false,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:'🇲🇳',fitzpatrick_scale:false,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:'🇲🇪',fitzpatrick_scale:false,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:'🇲🇸',fitzpatrick_scale:false,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:'🇲🇦',fitzpatrick_scale:false,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:'🇲🇿',fitzpatrick_scale:false,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:'🇲🇲',fitzpatrick_scale:false,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:'🇳🇦',fitzpatrick_scale:false,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:'🇳🇷',fitzpatrick_scale:false,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:'🇳🇵',fitzpatrick_scale:false,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:'🇳🇱',fitzpatrick_scale:false,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:'🇳🇨',fitzpatrick_scale:false,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:'🇳🇿',fitzpatrick_scale:false,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:'🇳🇮',fitzpatrick_scale:false,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:'🇳🇪',fitzpatrick_scale:false,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:'🇳🇬',fitzpatrick_scale:false,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:'🇳🇺',fitzpatrick_scale:false,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:'🇳🇫',fitzpatrick_scale:false,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:'🇲🇵',fitzpatrick_scale:false,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:'🇰🇵',fitzpatrick_scale:false,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:'🇳🇴',fitzpatrick_scale:false,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:'🇴🇲',fitzpatrick_scale:false,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:'🇵🇰',fitzpatrick_scale:false,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:'🇵🇼',fitzpatrick_scale:false,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:'🇵🇸',fitzpatrick_scale:false,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:'🇵🇦',fitzpatrick_scale:false,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:'🇵🇬',fitzpatrick_scale:false,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:'🇵🇾',fitzpatrick_scale:false,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:'🇵🇪',fitzpatrick_scale:false,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:'🇵🇭',fitzpatrick_scale:false,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:'🇵🇳',fitzpatrick_scale:false,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:'🇵🇱',fitzpatrick_scale:false,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:'🇵🇹',fitzpatrick_scale:false,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:'🇵🇷',fitzpatrick_scale:false,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:'🇶🇦',fitzpatrick_scale:false,category:"flags"},reunion:{keywords:["réunion","flag","nation","country","banner"],char:'🇷🇪',fitzpatrick_scale:false,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:'🇷🇴',fitzpatrick_scale:false,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:'🇷🇺',fitzpatrick_scale:false,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:'🇷🇼',fitzpatrick_scale:false,category:"flags"},st_barthelemy:{keywords:["saint","barthélemy","flag","nation","country","banner"],char:'🇧🇱',fitzpatrick_scale:false,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:'🇸🇭',fitzpatrick_scale:false,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:'🇰🇳',fitzpatrick_scale:false,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:'🇱🇨',fitzpatrick_scale:false,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:'🇵🇲',fitzpatrick_scale:false,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:'🇻🇨',fitzpatrick_scale:false,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:'🇼🇸',fitzpatrick_scale:false,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:'🇸🇲',fitzpatrick_scale:false,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:'🇸🇹',fitzpatrick_scale:false,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:'🇸🇦',fitzpatrick_scale:false,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:'🇸🇳',fitzpatrick_scale:false,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:'🇷🇸',fitzpatrick_scale:false,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:'🇸🇨',fitzpatrick_scale:false,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:'🇸🇱',fitzpatrick_scale:false,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:'🇸🇬',fitzpatrick_scale:false,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:'🇸🇽',fitzpatrick_scale:false,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:'🇸🇰',fitzpatrick_scale:false,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:'🇸🇮',fitzpatrick_scale:false,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:'🇸🇧',fitzpatrick_scale:false,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:'🇸🇴',fitzpatrick_scale:false,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:'🇿🇦',fitzpatrick_scale:false,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:'🇬🇸',fitzpatrick_scale:false,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:'🇰🇷',fitzpatrick_scale:false,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:'🇸🇸',fitzpatrick_scale:false,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:'🇪🇸',fitzpatrick_scale:false,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:'🇱🇰',fitzpatrick_scale:false,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:'🇸🇩',fitzpatrick_scale:false,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:'🇸🇷',fitzpatrick_scale:false,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:'🇸🇿',fitzpatrick_scale:false,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:'🇸🇪',fitzpatrick_scale:false,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:'🇨🇭',fitzpatrick_scale:false,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:'🇸🇾',fitzpatrick_scale:false,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:'🇹🇼',fitzpatrick_scale:false,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:'🇹🇯',fitzpatrick_scale:false,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:'🇹🇿',fitzpatrick_scale:false,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:'🇹🇭',fitzpatrick_scale:false,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:'🇹🇱',fitzpatrick_scale:false,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:'🇹🇬',fitzpatrick_scale:false,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:'🇹🇰',fitzpatrick_scale:false,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:'🇹🇴',fitzpatrick_scale:false,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:'🇹🇹',fitzpatrick_scale:false,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:'🇹🇳',fitzpatrick_scale:false,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:'🇹🇷',fitzpatrick_scale:false,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:'🇹🇲',fitzpatrick_scale:false,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:'🇹🇨',fitzpatrick_scale:false,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:'🇹🇻',fitzpatrick_scale:false,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:'🇺🇬',fitzpatrick_scale:false,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:'🇺🇦',fitzpatrick_scale:false,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:'🇦🇪',fitzpatrick_scale:false,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:'🇬🇧',fitzpatrick_scale:false,category:"flags"},england:{keywords:["flag","english"],char:'🏴󠁧󠁢󠁥󠁮󠁧󠁿',fitzpatrick_scale:false,category:"flags"},scotland:{keywords:["flag","scottish"],char:'🏴󠁧󠁢󠁳󠁣󠁴󠁿',fitzpatrick_scale:false,category:"flags"},wales:{keywords:["flag","welsh"],char:'🏴󠁧󠁢󠁷󠁬󠁳󠁿',fitzpatrick_scale:false,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:'🇺🇸',fitzpatrick_scale:false,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:'🇻🇮',fitzpatrick_scale:false,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:'🇺🇾',fitzpatrick_scale:false,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:'🇺🇿',fitzpatrick_scale:false,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:'🇻🇺',fitzpatrick_scale:false,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:'🇻🇦',fitzpatrick_scale:false,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:'🇻🇪',fitzpatrick_scale:false,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:'🇻🇳',fitzpatrick_scale:false,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:'🇼🇫',fitzpatrick_scale:false,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:'🇪🇭',fitzpatrick_scale:false,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:'🇾🇪',fitzpatrick_scale:false,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:'🇿🇲',fitzpatrick_scale:false,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:'🇿🇼',fitzpatrick_scale:false,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:'🇺🇳',fitzpatrick_scale:false,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:'🏴‍☠️',fitzpatrick_scale:false,category:"flags"}}); ================================================ FILE: apps/web-antd/public/tinymce/plugins/emoticons/js/emojis.js ================================================ window.tinymce.Resource.add("tinymce.plugins.emoticons",{grinning:{keywords:["face","smile","happy","joy",":D","grin"],char:"😀",fitzpatrick_scale:false,category:"people"},grimacing:{keywords:["face","grimace","teeth"],char:"😬",fitzpatrick_scale:false,category:"people"},grin:{keywords:["face","happy","smile","joy","kawaii"],char:"😁",fitzpatrick_scale:false,category:"people"},joy:{keywords:["face","cry","tears","weep","happy","happytears","haha"],char:"😂",fitzpatrick_scale:false,category:"people"},rofl:{keywords:["face","rolling","floor","laughing","lol","haha"],char:"🤣",fitzpatrick_scale:false,category:"people"},partying:{keywords:["face","celebration","woohoo"],char:"🥳",fitzpatrick_scale:false,category:"people"},smiley:{keywords:["face","happy","joy","haha",":D",":)","smile","funny"],char:"😃",fitzpatrick_scale:false,category:"people"},smile:{keywords:["face","happy","joy","funny","haha","laugh","like",":D",":)"],char:"😄",fitzpatrick_scale:false,category:"people"},sweat_smile:{keywords:["face","hot","happy","laugh","sweat","smile","relief"],char:"😅",fitzpatrick_scale:false,category:"people"},laughing:{keywords:["happy","joy","lol","satisfied","haha","face","glad","XD","laugh"],char:"😆",fitzpatrick_scale:false,category:"people"},innocent:{keywords:["face","angel","heaven","halo"],char:"😇",fitzpatrick_scale:false,category:"people"},wink:{keywords:["face","happy","mischievous","secret",";)","smile","eye"],char:"😉",fitzpatrick_scale:false,category:"people"},blush:{keywords:["face","smile","happy","flushed","crush","embarrassed","shy","joy"],char:"😊",fitzpatrick_scale:false,category:"people"},slightly_smiling_face:{keywords:["face","smile"],char:"🙂",fitzpatrick_scale:false,category:"people"},upside_down_face:{keywords:["face","flipped","silly","smile"],char:"🙃",fitzpatrick_scale:false,category:"people"},relaxed:{keywords:["face","blush","massage","happiness"],char:"☺️",fitzpatrick_scale:false,category:"people"},yum:{keywords:["happy","joy","tongue","smile","face","silly","yummy","nom","delicious","savouring"],char:"😋",fitzpatrick_scale:false,category:"people"},relieved:{keywords:["face","relaxed","phew","massage","happiness"],char:"😌",fitzpatrick_scale:false,category:"people"},heart_eyes:{keywords:["face","love","like","affection","valentines","infatuation","crush","heart"],char:"😍",fitzpatrick_scale:false,category:"people"},smiling_face_with_three_hearts:{keywords:["face","love","like","affection","valentines","infatuation","crush","hearts","adore"],char:"🥰",fitzpatrick_scale:false,category:"people"},kissing_heart:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"😘",fitzpatrick_scale:false,category:"people"},kissing:{keywords:["love","like","face","3","valentines","infatuation","kiss"],char:"😗",fitzpatrick_scale:false,category:"people"},kissing_smiling_eyes:{keywords:["face","affection","valentines","infatuation","kiss"],char:"😙",fitzpatrick_scale:false,category:"people"},kissing_closed_eyes:{keywords:["face","love","like","affection","valentines","infatuation","kiss"],char:"😚",fitzpatrick_scale:false,category:"people"},stuck_out_tongue_winking_eye:{keywords:["face","prank","childish","playful","mischievous","smile","wink","tongue"],char:"😜",fitzpatrick_scale:false,category:"people"},zany:{keywords:["face","goofy","crazy"],char:"🤪",fitzpatrick_scale:false,category:"people"},raised_eyebrow:{keywords:["face","distrust","scepticism","disapproval","disbelief","surprise"],char:"🤨",fitzpatrick_scale:false,category:"people"},monocle:{keywords:["face","stuffy","wealthy"],char:"🧐",fitzpatrick_scale:false,category:"people"},stuck_out_tongue_closed_eyes:{keywords:["face","prank","playful","mischievous","smile","tongue"],char:"😝",fitzpatrick_scale:false,category:"people"},stuck_out_tongue:{keywords:["face","prank","childish","playful","mischievous","smile","tongue"],char:"😛",fitzpatrick_scale:false,category:"people"},money_mouth_face:{keywords:["face","rich","dollar","money"],char:"🤑",fitzpatrick_scale:false,category:"people"},nerd_face:{keywords:["face","nerdy","geek","dork"],char:"🤓",fitzpatrick_scale:false,category:"people"},sunglasses:{keywords:["face","cool","smile","summer","beach","sunglass"],char:"😎",fitzpatrick_scale:false,category:"people"},star_struck:{keywords:["face","smile","starry","eyes","grinning"],char:"🤩",fitzpatrick_scale:false,category:"people"},clown_face:{keywords:["face"],char:"🤡",fitzpatrick_scale:false,category:"people"},cowboy_hat_face:{keywords:["face","cowgirl","hat"],char:"🤠",fitzpatrick_scale:false,category:"people"},hugs:{keywords:["face","smile","hug"],char:"🤗",fitzpatrick_scale:false,category:"people"},smirk:{keywords:["face","smile","mean","prank","smug","sarcasm"],char:"😏",fitzpatrick_scale:false,category:"people"},no_mouth:{keywords:["face","hellokitty"],char:"😶",fitzpatrick_scale:false,category:"people"},neutral_face:{keywords:["indifference","meh",":|","neutral"],char:"😐",fitzpatrick_scale:false,category:"people"},expressionless:{keywords:["face","indifferent","-_-","meh","deadpan"],char:"😑",fitzpatrick_scale:false,category:"people"},unamused:{keywords:["indifference","bored","straight face","serious","sarcasm","unimpressed","skeptical","dubious","side_eye"],char:"😒",fitzpatrick_scale:false,category:"people"},roll_eyes:{keywords:["face","eyeroll","frustrated"],char:"🙄",fitzpatrick_scale:false,category:"people"},thinking:{keywords:["face","hmmm","think","consider"],char:"🤔",fitzpatrick_scale:false,category:"people"},lying_face:{keywords:["face","lie","pinocchio"],char:"🤥",fitzpatrick_scale:false,category:"people"},hand_over_mouth:{keywords:["face","whoops","shock","surprise"],char:"🤭",fitzpatrick_scale:false,category:"people"},shushing:{keywords:["face","quiet","shhh"],char:"🤫",fitzpatrick_scale:false,category:"people"},symbols_over_mouth:{keywords:["face","swearing","cursing","cussing","profanity","expletive"],char:"🤬",fitzpatrick_scale:false,category:"people"},exploding_head:{keywords:["face","shocked","mind","blown"],char:"🤯",fitzpatrick_scale:false,category:"people"},flushed:{keywords:["face","blush","shy","flattered"],char:"😳",fitzpatrick_scale:false,category:"people"},disappointed:{keywords:["face","sad","upset","depressed",":("],char:"😞",fitzpatrick_scale:false,category:"people"},worried:{keywords:["face","concern","nervous",":("],char:"😟",fitzpatrick_scale:false,category:"people"},angry:{keywords:["mad","face","annoyed","frustrated"],char:"😠",fitzpatrick_scale:false,category:"people"},rage:{keywords:["angry","mad","hate","despise"],char:"😡",fitzpatrick_scale:false,category:"people"},pensive:{keywords:["face","sad","depressed","upset"],char:"😔",fitzpatrick_scale:false,category:"people"},confused:{keywords:["face","indifference","huh","weird","hmmm",":/"],char:"😕",fitzpatrick_scale:false,category:"people"},slightly_frowning_face:{keywords:["face","frowning","disappointed","sad","upset"],char:"🙁",fitzpatrick_scale:false,category:"people"},frowning_face:{keywords:["face","sad","upset","frown"],char:"☹",fitzpatrick_scale:false,category:"people"},persevere:{keywords:["face","sick","no","upset","oops"],char:"😣",fitzpatrick_scale:false,category:"people"},confounded:{keywords:["face","confused","sick","unwell","oops",":S"],char:"😖",fitzpatrick_scale:false,category:"people"},tired_face:{keywords:["sick","whine","upset","frustrated"],char:"😫",fitzpatrick_scale:false,category:"people"},weary:{keywords:["face","tired","sleepy","sad","frustrated","upset"],char:"😩",fitzpatrick_scale:false,category:"people"},pleading:{keywords:["face","begging","mercy"],char:"🥺",fitzpatrick_scale:false,category:"people"},triumph:{keywords:["face","gas","phew","proud","pride"],char:"😤",fitzpatrick_scale:false,category:"people"},open_mouth:{keywords:["face","surprise","impressed","wow","whoa",":O"],char:"😮",fitzpatrick_scale:false,category:"people"},scream:{keywords:["face","munch","scared","omg"],char:"😱",fitzpatrick_scale:false,category:"people"},fearful:{keywords:["face","scared","terrified","nervous","oops","huh"],char:"😨",fitzpatrick_scale:false,category:"people"},cold_sweat:{keywords:["face","nervous","sweat"],char:"😰",fitzpatrick_scale:false,category:"people"},hushed:{keywords:["face","woo","shh"],char:"😯",fitzpatrick_scale:false,category:"people"},frowning:{keywords:["face","aw","what"],char:"😦",fitzpatrick_scale:false,category:"people"},anguished:{keywords:["face","stunned","nervous"],char:"😧",fitzpatrick_scale:false,category:"people"},cry:{keywords:["face","tears","sad","depressed","upset",":'("],char:"😢",fitzpatrick_scale:false,category:"people"},disappointed_relieved:{keywords:["face","phew","sweat","nervous"],char:"😥",fitzpatrick_scale:false,category:"people"},drooling_face:{keywords:["face"],char:"🤤",fitzpatrick_scale:false,category:"people"},sleepy:{keywords:["face","tired","rest","nap"],char:"😪",fitzpatrick_scale:false,category:"people"},sweat:{keywords:["face","hot","sad","tired","exercise"],char:"😓",fitzpatrick_scale:false,category:"people"},hot:{keywords:["face","feverish","heat","red","sweating"],char:"🥵",fitzpatrick_scale:false,category:"people"},cold:{keywords:["face","blue","freezing","frozen","frostbite","icicles"],char:"🥶",fitzpatrick_scale:false,category:"people"},sob:{keywords:["face","cry","tears","sad","upset","depressed"],char:"😭",fitzpatrick_scale:false,category:"people"},dizzy_face:{keywords:["spent","unconscious","xox","dizzy"],char:"😵",fitzpatrick_scale:false,category:"people"},astonished:{keywords:["face","xox","surprised","poisoned"],char:"😲",fitzpatrick_scale:false,category:"people"},zipper_mouth_face:{keywords:["face","sealed","zipper","secret"],char:"🤐",fitzpatrick_scale:false,category:"people"},nauseated_face:{keywords:["face","vomit","gross","green","sick","throw up","ill"],char:"🤢",fitzpatrick_scale:false,category:"people"},sneezing_face:{keywords:["face","gesundheit","sneeze","sick","allergy"],char:"🤧",fitzpatrick_scale:false,category:"people"},vomiting:{keywords:["face","sick"],char:"🤮",fitzpatrick_scale:false,category:"people"},mask:{keywords:["face","sick","ill","disease"],char:"😷",fitzpatrick_scale:false,category:"people"},face_with_thermometer:{keywords:["sick","temperature","thermometer","cold","fever"],char:"🤒",fitzpatrick_scale:false,category:"people"},face_with_head_bandage:{keywords:["injured","clumsy","bandage","hurt"],char:"🤕",fitzpatrick_scale:false,category:"people"},woozy:{keywords:["face","dizzy","intoxicated","tipsy","wavy"],char:"🥴",fitzpatrick_scale:false,category:"people"},sleeping:{keywords:["face","tired","sleepy","night","zzz"],char:"😴",fitzpatrick_scale:false,category:"people"},zzz:{keywords:["sleepy","tired","dream"],char:"💤",fitzpatrick_scale:false,category:"people"},poop:{keywords:["hankey","shitface","fail","turd","shit"],char:"💩",fitzpatrick_scale:false,category:"people"},smiling_imp:{keywords:["devil","horns"],char:"😈",fitzpatrick_scale:false,category:"people"},imp:{keywords:["devil","angry","horns"],char:"👿",fitzpatrick_scale:false,category:"people"},japanese_ogre:{keywords:["monster","red","mask","halloween","scary","creepy","devil","demon","japanese","ogre"],char:"👹",fitzpatrick_scale:false,category:"people"},japanese_goblin:{keywords:["red","evil","mask","monster","scary","creepy","japanese","goblin"],char:"👺",fitzpatrick_scale:false,category:"people"},skull:{keywords:["dead","skeleton","creepy","death"],char:"💀",fitzpatrick_scale:false,category:"people"},ghost:{keywords:["halloween","spooky","scary"],char:"👻",fitzpatrick_scale:false,category:"people"},alien:{keywords:["UFO","paul","weird","outer_space"],char:"👽",fitzpatrick_scale:false,category:"people"},robot:{keywords:["computer","machine","bot"],char:"🤖",fitzpatrick_scale:false,category:"people"},smiley_cat:{keywords:["animal","cats","happy","smile"],char:"😺",fitzpatrick_scale:false,category:"people"},smile_cat:{keywords:["animal","cats","smile"],char:"😸",fitzpatrick_scale:false,category:"people"},joy_cat:{keywords:["animal","cats","haha","happy","tears"],char:"😹",fitzpatrick_scale:false,category:"people"},heart_eyes_cat:{keywords:["animal","love","like","affection","cats","valentines","heart"],char:"😻",fitzpatrick_scale:false,category:"people"},smirk_cat:{keywords:["animal","cats","smirk"],char:"😼",fitzpatrick_scale:false,category:"people"},kissing_cat:{keywords:["animal","cats","kiss"],char:"😽",fitzpatrick_scale:false,category:"people"},scream_cat:{keywords:["animal","cats","munch","scared","scream"],char:"🙀",fitzpatrick_scale:false,category:"people"},crying_cat_face:{keywords:["animal","tears","weep","sad","cats","upset","cry"],char:"😿",fitzpatrick_scale:false,category:"people"},pouting_cat:{keywords:["animal","cats"],char:"😾",fitzpatrick_scale:false,category:"people"},palms_up:{keywords:["hands","gesture","cupped","prayer"],char:"🤲",fitzpatrick_scale:true,category:"people"},raised_hands:{keywords:["gesture","hooray","yea","celebration","hands"],char:"🙌",fitzpatrick_scale:true,category:"people"},clap:{keywords:["hands","praise","applause","congrats","yay"],char:"👏",fitzpatrick_scale:true,category:"people"},wave:{keywords:["hands","gesture","goodbye","solong","farewell","hello","hi","palm"],char:"👋",fitzpatrick_scale:true,category:"people"},call_me_hand:{keywords:["hands","gesture"],char:"🤙",fitzpatrick_scale:true,category:"people"},"+1":{keywords:["thumbsup","yes","awesome","good","agree","accept","cool","hand","like"],char:"👍",fitzpatrick_scale:true,category:"people"},"-1":{keywords:["thumbsdown","no","dislike","hand"],char:"👎",fitzpatrick_scale:true,category:"people"},facepunch:{keywords:["angry","violence","fist","hit","attack","hand"],char:"👊",fitzpatrick_scale:true,category:"people"},fist:{keywords:["fingers","hand","grasp"],char:"✊",fitzpatrick_scale:true,category:"people"},fist_left:{keywords:["hand","fistbump"],char:"🤛",fitzpatrick_scale:true,category:"people"},fist_right:{keywords:["hand","fistbump"],char:"🤜",fitzpatrick_scale:true,category:"people"},v:{keywords:["fingers","ohyeah","hand","peace","victory","two"],char:"✌",fitzpatrick_scale:true,category:"people"},ok_hand:{keywords:["fingers","limbs","perfect","ok","okay"],char:"👌",fitzpatrick_scale:true,category:"people"},raised_hand:{keywords:["fingers","stop","highfive","palm","ban"],char:"✋",fitzpatrick_scale:true,category:"people"},raised_back_of_hand:{keywords:["fingers","raised","backhand"],char:"🤚",fitzpatrick_scale:true,category:"people"},open_hands:{keywords:["fingers","butterfly","hands","open"],char:"👐",fitzpatrick_scale:true,category:"people"},muscle:{keywords:["arm","flex","hand","summer","strong","biceps"],char:"💪",fitzpatrick_scale:true,category:"people"},pray:{keywords:["please","hope","wish","namaste","highfive"],char:"🙏",fitzpatrick_scale:true,category:"people"},foot:{keywords:["kick","stomp"],char:"🦶",fitzpatrick_scale:true,category:"people"},leg:{keywords:["kick","limb"],char:"🦵",fitzpatrick_scale:true,category:"people"},handshake:{keywords:["agreement","shake"],char:"🤝",fitzpatrick_scale:false,category:"people"},point_up:{keywords:["hand","fingers","direction","up"],char:"☝",fitzpatrick_scale:true,category:"people"},point_up_2:{keywords:["fingers","hand","direction","up"],char:"👆",fitzpatrick_scale:true,category:"people"},point_down:{keywords:["fingers","hand","direction","down"],char:"👇",fitzpatrick_scale:true,category:"people"},point_left:{keywords:["direction","fingers","hand","left"],char:"👈",fitzpatrick_scale:true,category:"people"},point_right:{keywords:["fingers","hand","direction","right"],char:"👉",fitzpatrick_scale:true,category:"people"},fu:{keywords:["hand","fingers","rude","middle","flipping"],char:"🖕",fitzpatrick_scale:true,category:"people"},raised_hand_with_fingers_splayed:{keywords:["hand","fingers","palm"],char:"🖐",fitzpatrick_scale:true,category:"people"},love_you:{keywords:["hand","fingers","gesture"],char:"🤟",fitzpatrick_scale:true,category:"people"},metal:{keywords:["hand","fingers","evil_eye","sign_of_horns","rock_on"],char:"🤘",fitzpatrick_scale:true,category:"people"},crossed_fingers:{keywords:["good","lucky"],char:"🤞",fitzpatrick_scale:true,category:"people"},vulcan_salute:{keywords:["hand","fingers","spock","star trek"],char:"🖖",fitzpatrick_scale:true,category:"people"},writing_hand:{keywords:["lower_left_ballpoint_pen","stationery","write","compose"],char:"✍",fitzpatrick_scale:true,category:"people"},selfie:{keywords:["camera","phone"],char:"🤳",fitzpatrick_scale:true,category:"people"},nail_care:{keywords:["beauty","manicure","finger","fashion","nail"],char:"💅",fitzpatrick_scale:true,category:"people"},lips:{keywords:["mouth","kiss"],char:"👄",fitzpatrick_scale:false,category:"people"},tooth:{keywords:["teeth","dentist"],char:"🦷",fitzpatrick_scale:false,category:"people"},tongue:{keywords:["mouth","playful"],char:"👅",fitzpatrick_scale:false,category:"people"},ear:{keywords:["face","hear","sound","listen"],char:"👂",fitzpatrick_scale:true,category:"people"},nose:{keywords:["smell","sniff"],char:"👃",fitzpatrick_scale:true,category:"people"},eye:{keywords:["face","look","see","watch","stare"],char:"👁",fitzpatrick_scale:false,category:"people"},eyes:{keywords:["look","watch","stalk","peek","see"],char:"👀",fitzpatrick_scale:false,category:"people"},brain:{keywords:["smart","intelligent"],char:"🧠",fitzpatrick_scale:false,category:"people"},bust_in_silhouette:{keywords:["user","person","human"],char:"👤",fitzpatrick_scale:false,category:"people"},busts_in_silhouette:{keywords:["user","person","human","group","team"],char:"👥",fitzpatrick_scale:false,category:"people"},speaking_head:{keywords:["user","person","human","sing","say","talk"],char:"🗣",fitzpatrick_scale:false,category:"people"},baby:{keywords:["child","boy","girl","toddler"],char:"👶",fitzpatrick_scale:true,category:"people"},child:{keywords:["gender-neutral","young"],char:"🧒",fitzpatrick_scale:true,category:"people"},boy:{keywords:["man","male","guy","teenager"],char:"👦",fitzpatrick_scale:true,category:"people"},girl:{keywords:["female","woman","teenager"],char:"👧",fitzpatrick_scale:true,category:"people"},adult:{keywords:["gender-neutral","person"],char:"🧑",fitzpatrick_scale:true,category:"people"},man:{keywords:["mustache","father","dad","guy","classy","sir","moustache"],char:"👨",fitzpatrick_scale:true,category:"people"},woman:{keywords:["female","girls","lady"],char:"👩",fitzpatrick_scale:true,category:"people"},blonde_woman:{keywords:["woman","female","girl","blonde","person"],char:"👱‍♀️",fitzpatrick_scale:true,category:"people"},blonde_man:{keywords:["man","male","boy","blonde","guy","person"],char:"👱",fitzpatrick_scale:true,category:"people"},bearded_person:{keywords:["person","bewhiskered"],char:"🧔",fitzpatrick_scale:true,category:"people"},older_adult:{keywords:["human","elder","senior","gender-neutral"],char:"🧓",fitzpatrick_scale:true,category:"people"},older_man:{keywords:["human","male","men","old","elder","senior"],char:"👴",fitzpatrick_scale:true,category:"people"},older_woman:{keywords:["human","female","women","lady","old","elder","senior"],char:"👵",fitzpatrick_scale:true,category:"people"},man_with_gua_pi_mao:{keywords:["male","boy","chinese"],char:"👲",fitzpatrick_scale:true,category:"people"},woman_with_headscarf:{keywords:["female","hijab","mantilla","tichel"],char:"🧕",fitzpatrick_scale:true,category:"people"},woman_with_turban:{keywords:["female","indian","hinduism","arabs","woman"],char:"👳‍♀️",fitzpatrick_scale:true,category:"people"},man_with_turban:{keywords:["male","indian","hinduism","arabs"],char:"👳",fitzpatrick_scale:true,category:"people"},policewoman:{keywords:["woman","police","law","legal","enforcement","arrest","911","female"],char:"👮‍♀️",fitzpatrick_scale:true,category:"people"},policeman:{keywords:["man","police","law","legal","enforcement","arrest","911"],char:"👮",fitzpatrick_scale:true,category:"people"},construction_worker_woman:{keywords:["female","human","wip","build","construction","worker","labor","woman"],char:"👷‍♀️",fitzpatrick_scale:true,category:"people"},construction_worker_man:{keywords:["male","human","wip","guy","build","construction","worker","labor"],char:"👷",fitzpatrick_scale:true,category:"people"},guardswoman:{keywords:["uk","gb","british","female","royal","woman"],char:"💂‍♀️",fitzpatrick_scale:true,category:"people"},guardsman:{keywords:["uk","gb","british","male","guy","royal"],char:"💂",fitzpatrick_scale:true,category:"people"},female_detective:{keywords:["human","spy","detective","female","woman"],char:"🕵️‍♀️",fitzpatrick_scale:true,category:"people"},male_detective:{keywords:["human","spy","detective"],char:"🕵",fitzpatrick_scale:true,category:"people"},woman_health_worker:{keywords:["doctor","nurse","therapist","healthcare","woman","human"],char:"👩‍⚕️",fitzpatrick_scale:true,category:"people"},man_health_worker:{keywords:["doctor","nurse","therapist","healthcare","man","human"],char:"👨‍⚕️",fitzpatrick_scale:true,category:"people"},woman_farmer:{keywords:["rancher","gardener","woman","human"],char:"👩‍🌾",fitzpatrick_scale:true,category:"people"},man_farmer:{keywords:["rancher","gardener","man","human"],char:"👨‍🌾",fitzpatrick_scale:true,category:"people"},woman_cook:{keywords:["chef","woman","human"],char:"👩‍🍳",fitzpatrick_scale:true,category:"people"},man_cook:{keywords:["chef","man","human"],char:"👨‍🍳",fitzpatrick_scale:true,category:"people"},woman_student:{keywords:["graduate","woman","human"],char:"👩‍🎓",fitzpatrick_scale:true,category:"people"},man_student:{keywords:["graduate","man","human"],char:"👨‍🎓",fitzpatrick_scale:true,category:"people"},woman_singer:{keywords:["rockstar","entertainer","woman","human"],char:"👩‍🎤",fitzpatrick_scale:true,category:"people"},man_singer:{keywords:["rockstar","entertainer","man","human"],char:"👨‍🎤",fitzpatrick_scale:true,category:"people"},woman_teacher:{keywords:["instructor","professor","woman","human"],char:"👩‍🏫",fitzpatrick_scale:true,category:"people"},man_teacher:{keywords:["instructor","professor","man","human"],char:"👨‍🏫",fitzpatrick_scale:true,category:"people"},woman_factory_worker:{keywords:["assembly","industrial","woman","human"],char:"👩‍🏭",fitzpatrick_scale:true,category:"people"},man_factory_worker:{keywords:["assembly","industrial","man","human"],char:"👨‍🏭",fitzpatrick_scale:true,category:"people"},woman_technologist:{keywords:["coder","developer","engineer","programmer","software","woman","human","laptop","computer"],char:"👩‍💻",fitzpatrick_scale:true,category:"people"},man_technologist:{keywords:["coder","developer","engineer","programmer","software","man","human","laptop","computer"],char:"👨‍💻",fitzpatrick_scale:true,category:"people"},woman_office_worker:{keywords:["business","manager","woman","human"],char:"👩‍💼",fitzpatrick_scale:true,category:"people"},man_office_worker:{keywords:["business","manager","man","human"],char:"👨‍💼",fitzpatrick_scale:true,category:"people"},woman_mechanic:{keywords:["plumber","woman","human","wrench"],char:"👩‍🔧",fitzpatrick_scale:true,category:"people"},man_mechanic:{keywords:["plumber","man","human","wrench"],char:"👨‍🔧",fitzpatrick_scale:true,category:"people"},woman_scientist:{keywords:["biologist","chemist","engineer","physicist","woman","human"],char:"👩‍🔬",fitzpatrick_scale:true,category:"people"},man_scientist:{keywords:["biologist","chemist","engineer","physicist","man","human"],char:"👨‍🔬",fitzpatrick_scale:true,category:"people"},woman_artist:{keywords:["painter","woman","human"],char:"👩‍🎨",fitzpatrick_scale:true,category:"people"},man_artist:{keywords:["painter","man","human"],char:"👨‍🎨",fitzpatrick_scale:true,category:"people"},woman_firefighter:{keywords:["fireman","woman","human"],char:"👩‍🚒",fitzpatrick_scale:true,category:"people"},man_firefighter:{keywords:["fireman","man","human"],char:"👨‍🚒",fitzpatrick_scale:true,category:"people"},woman_pilot:{keywords:["aviator","plane","woman","human"],char:"👩‍✈️",fitzpatrick_scale:true,category:"people"},man_pilot:{keywords:["aviator","plane","man","human"],char:"👨‍✈️",fitzpatrick_scale:true,category:"people"},woman_astronaut:{keywords:["space","rocket","woman","human"],char:"👩‍🚀",fitzpatrick_scale:true,category:"people"},man_astronaut:{keywords:["space","rocket","man","human"],char:"👨‍🚀",fitzpatrick_scale:true,category:"people"},woman_judge:{keywords:["justice","court","woman","human"],char:"👩‍⚖️",fitzpatrick_scale:true,category:"people"},man_judge:{keywords:["justice","court","man","human"],char:"👨‍⚖️",fitzpatrick_scale:true,category:"people"},woman_superhero:{keywords:["woman","female","good","heroine","superpowers"],char:"🦸‍♀️",fitzpatrick_scale:true,category:"people"},man_superhero:{keywords:["man","male","good","hero","superpowers"],char:"🦸‍♂️",fitzpatrick_scale:true,category:"people"},woman_supervillain:{keywords:["woman","female","evil","bad","criminal","heroine","superpowers"],char:"🦹‍♀️",fitzpatrick_scale:true,category:"people"},man_supervillain:{keywords:["man","male","evil","bad","criminal","hero","superpowers"],char:"🦹‍♂️",fitzpatrick_scale:true,category:"people"},mrs_claus:{keywords:["woman","female","xmas","mother christmas"],char:"🤶",fitzpatrick_scale:true,category:"people"},santa:{keywords:["festival","man","male","xmas","father christmas"],char:"🎅",fitzpatrick_scale:true,category:"people"},sorceress:{keywords:["woman","female","mage","witch"],char:"🧙‍♀️",fitzpatrick_scale:true,category:"people"},wizard:{keywords:["man","male","mage","sorcerer"],char:"🧙‍♂️",fitzpatrick_scale:true,category:"people"},woman_elf:{keywords:["woman","female"],char:"🧝‍♀️",fitzpatrick_scale:true,category:"people"},man_elf:{keywords:["man","male"],char:"🧝‍♂️",fitzpatrick_scale:true,category:"people"},woman_vampire:{keywords:["woman","female"],char:"🧛‍♀️",fitzpatrick_scale:true,category:"people"},man_vampire:{keywords:["man","male","dracula"],char:"🧛‍♂️",fitzpatrick_scale:true,category:"people"},woman_zombie:{keywords:["woman","female","undead","walking dead"],char:"🧟‍♀️",fitzpatrick_scale:false,category:"people"},man_zombie:{keywords:["man","male","dracula","undead","walking dead"],char:"🧟‍♂️",fitzpatrick_scale:false,category:"people"},woman_genie:{keywords:["woman","female"],char:"🧞‍♀️",fitzpatrick_scale:false,category:"people"},man_genie:{keywords:["man","male"],char:"🧞‍♂️",fitzpatrick_scale:false,category:"people"},mermaid:{keywords:["woman","female","merwoman","ariel"],char:"🧜‍♀️",fitzpatrick_scale:true,category:"people"},merman:{keywords:["man","male","triton"],char:"🧜‍♂️",fitzpatrick_scale:true,category:"people"},woman_fairy:{keywords:["woman","female"],char:"🧚‍♀️",fitzpatrick_scale:true,category:"people"},man_fairy:{keywords:["man","male"],char:"🧚‍♂️",fitzpatrick_scale:true,category:"people"},angel:{keywords:["heaven","wings","halo"],char:"👼",fitzpatrick_scale:true,category:"people"},pregnant_woman:{keywords:["baby"],char:"🤰",fitzpatrick_scale:true,category:"people"},breastfeeding:{keywords:["nursing","baby"],char:"🤱",fitzpatrick_scale:true,category:"people"},princess:{keywords:["girl","woman","female","blond","crown","royal","queen"],char:"👸",fitzpatrick_scale:true,category:"people"},prince:{keywords:["boy","man","male","crown","royal","king"],char:"🤴",fitzpatrick_scale:true,category:"people"},bride_with_veil:{keywords:["couple","marriage","wedding","woman","bride"],char:"👰",fitzpatrick_scale:true,category:"people"},man_in_tuxedo:{keywords:["couple","marriage","wedding","groom"],char:"🤵",fitzpatrick_scale:true,category:"people"},running_woman:{keywords:["woman","walking","exercise","race","running","female"],char:"🏃‍♀️",fitzpatrick_scale:true,category:"people"},running_man:{keywords:["man","walking","exercise","race","running"],char:"🏃",fitzpatrick_scale:true,category:"people"},walking_woman:{keywords:["human","feet","steps","woman","female"],char:"🚶‍♀️",fitzpatrick_scale:true,category:"people"},walking_man:{keywords:["human","feet","steps"],char:"🚶",fitzpatrick_scale:true,category:"people"},dancer:{keywords:["female","girl","woman","fun"],char:"💃",fitzpatrick_scale:true,category:"people"},man_dancing:{keywords:["male","boy","fun","dancer"],char:"🕺",fitzpatrick_scale:true,category:"people"},dancing_women:{keywords:["female","bunny","women","girls"],char:"👯",fitzpatrick_scale:false,category:"people"},dancing_men:{keywords:["male","bunny","men","boys"],char:"👯‍♂️",fitzpatrick_scale:false,category:"people"},couple:{keywords:["pair","people","human","love","date","dating","like","affection","valentines","marriage"],char:"👫",fitzpatrick_scale:false,category:"people"},two_men_holding_hands:{keywords:["pair","couple","love","like","bromance","friendship","people","human"],char:"👬",fitzpatrick_scale:false,category:"people"},two_women_holding_hands:{keywords:["pair","friendship","couple","love","like","female","people","human"],char:"👭",fitzpatrick_scale:false,category:"people"},bowing_woman:{keywords:["woman","female","girl"],char:"🙇‍♀️",fitzpatrick_scale:true,category:"people"},bowing_man:{keywords:["man","male","boy"],char:"🙇",fitzpatrick_scale:true,category:"people"},man_facepalming:{keywords:["man","male","boy","disbelief"],char:"🤦‍♂️",fitzpatrick_scale:true,category:"people"},woman_facepalming:{keywords:["woman","female","girl","disbelief"],char:"🤦‍♀️",fitzpatrick_scale:true,category:"people"},woman_shrugging:{keywords:["woman","female","girl","confused","indifferent","doubt"],char:"🤷",fitzpatrick_scale:true,category:"people"},man_shrugging:{keywords:["man","male","boy","confused","indifferent","doubt"],char:"🤷‍♂️",fitzpatrick_scale:true,category:"people"},tipping_hand_woman:{keywords:["female","girl","woman","human","information"],char:"💁",fitzpatrick_scale:true,category:"people"},tipping_hand_man:{keywords:["male","boy","man","human","information"],char:"💁‍♂️",fitzpatrick_scale:true,category:"people"},no_good_woman:{keywords:["female","girl","woman","nope"],char:"🙅",fitzpatrick_scale:true,category:"people"},no_good_man:{keywords:["male","boy","man","nope"],char:"🙅‍♂️",fitzpatrick_scale:true,category:"people"},ok_woman:{keywords:["women","girl","female","pink","human","woman"],char:"🙆",fitzpatrick_scale:true,category:"people"},ok_man:{keywords:["men","boy","male","blue","human","man"],char:"🙆‍♂️",fitzpatrick_scale:true,category:"people"},raising_hand_woman:{keywords:["female","girl","woman"],char:"🙋",fitzpatrick_scale:true,category:"people"},raising_hand_man:{keywords:["male","boy","man"],char:"🙋‍♂️",fitzpatrick_scale:true,category:"people"},pouting_woman:{keywords:["female","girl","woman"],char:"🙎",fitzpatrick_scale:true,category:"people"},pouting_man:{keywords:["male","boy","man"],char:"🙎‍♂️",fitzpatrick_scale:true,category:"people"},frowning_woman:{keywords:["female","girl","woman","sad","depressed","discouraged","unhappy"],char:"🙍",fitzpatrick_scale:true,category:"people"},frowning_man:{keywords:["male","boy","man","sad","depressed","discouraged","unhappy"],char:"🙍‍♂️",fitzpatrick_scale:true,category:"people"},haircut_woman:{keywords:["female","girl","woman"],char:"💇",fitzpatrick_scale:true,category:"people"},haircut_man:{keywords:["male","boy","man"],char:"💇‍♂️",fitzpatrick_scale:true,category:"people"},massage_woman:{keywords:["female","girl","woman","head"],char:"💆",fitzpatrick_scale:true,category:"people"},massage_man:{keywords:["male","boy","man","head"],char:"💆‍♂️",fitzpatrick_scale:true,category:"people"},woman_in_steamy_room:{keywords:["female","woman","spa","steamroom","sauna"],char:"🧖‍♀️",fitzpatrick_scale:true,category:"people"},man_in_steamy_room:{keywords:["male","man","spa","steamroom","sauna"],char:"🧖‍♂️",fitzpatrick_scale:true,category:"people"},couple_with_heart_woman_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"💑",fitzpatrick_scale:false,category:"people"},couple_with_heart_woman_woman:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"👩‍❤️‍👩",fitzpatrick_scale:false,category:"people"},couple_with_heart_man_man:{keywords:["pair","love","like","affection","human","dating","valentines","marriage"],char:"👨‍❤️‍👨",fitzpatrick_scale:false,category:"people"},couplekiss_man_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"💏",fitzpatrick_scale:false,category:"people"},couplekiss_woman_woman:{keywords:["pair","valentines","love","like","dating","marriage"],char:"👩‍❤️‍💋‍👩",fitzpatrick_scale:false,category:"people"},couplekiss_man_man:{keywords:["pair","valentines","love","like","dating","marriage"],char:"👨‍❤️‍💋‍👨",fitzpatrick_scale:false,category:"people"},family_man_woman_boy:{keywords:["home","parents","child","mom","dad","father","mother","people","human"],char:"👪",fitzpatrick_scale:false,category:"people"},family_man_woman_girl:{keywords:["home","parents","people","human","child"],char:"👨‍👩‍👧",fitzpatrick_scale:false,category:"people"},family_man_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👩‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_man_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👩‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_man_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"👨‍👩‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_woman_woman_boy:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👦",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👧",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_boy:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_woman_woman_boy_boy:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_woman_woman_girl_girl:{keywords:["home","parents","people","human","children"],char:"👩‍👩‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_man_man_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👦",fitzpatrick_scale:false,category:"people"},family_man_man_girl:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👧",fitzpatrick_scale:false,category:"people"},family_man_man_girl_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_man_man_boy_boy:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_man_man_girl_girl:{keywords:["home","parents","people","human","children"],char:"👨‍👨‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_woman_boy:{keywords:["home","parent","people","human","child"],char:"👩‍👦",fitzpatrick_scale:false,category:"people"},family_woman_girl:{keywords:["home","parent","people","human","child"],char:"👩‍👧",fitzpatrick_scale:false,category:"people"},family_woman_girl_boy:{keywords:["home","parent","people","human","children"],char:"👩‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_woman_boy_boy:{keywords:["home","parent","people","human","children"],char:"👩‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_woman_girl_girl:{keywords:["home","parent","people","human","children"],char:"👩‍👧‍👧",fitzpatrick_scale:false,category:"people"},family_man_boy:{keywords:["home","parent","people","human","child"],char:"👨‍👦",fitzpatrick_scale:false,category:"people"},family_man_girl:{keywords:["home","parent","people","human","child"],char:"👨‍👧",fitzpatrick_scale:false,category:"people"},family_man_girl_boy:{keywords:["home","parent","people","human","children"],char:"👨‍👧‍👦",fitzpatrick_scale:false,category:"people"},family_man_boy_boy:{keywords:["home","parent","people","human","children"],char:"👨‍👦‍👦",fitzpatrick_scale:false,category:"people"},family_man_girl_girl:{keywords:["home","parent","people","human","children"],char:"👨‍👧‍👧",fitzpatrick_scale:false,category:"people"},yarn:{keywords:["ball","crochet","knit"],char:"🧶",fitzpatrick_scale:false,category:"people"},thread:{keywords:["needle","sewing","spool","string"],char:"🧵",fitzpatrick_scale:false,category:"people"},coat:{keywords:["jacket"],char:"🧥",fitzpatrick_scale:false,category:"people"},labcoat:{keywords:["doctor","experiment","scientist","chemist"],char:"🥼",fitzpatrick_scale:false,category:"people"},womans_clothes:{keywords:["fashion","shopping_bags","female"],char:"👚",fitzpatrick_scale:false,category:"people"},tshirt:{keywords:["fashion","cloth","casual","shirt","tee"],char:"👕",fitzpatrick_scale:false,category:"people"},jeans:{keywords:["fashion","shopping"],char:"👖",fitzpatrick_scale:false,category:"people"},necktie:{keywords:["shirt","suitup","formal","fashion","cloth","business"],char:"👔",fitzpatrick_scale:false,category:"people"},dress:{keywords:["clothes","fashion","shopping"],char:"👗",fitzpatrick_scale:false,category:"people"},bikini:{keywords:["swimming","female","woman","girl","fashion","beach","summer"],char:"👙",fitzpatrick_scale:false,category:"people"},kimono:{keywords:["dress","fashion","women","female","japanese"],char:"👘",fitzpatrick_scale:false,category:"people"},lipstick:{keywords:["female","girl","fashion","woman"],char:"💄",fitzpatrick_scale:false,category:"people"},kiss:{keywords:["face","lips","love","like","affection","valentines"],char:"💋",fitzpatrick_scale:false,category:"people"},footprints:{keywords:["feet","tracking","walking","beach"],char:"👣",fitzpatrick_scale:false,category:"people"},flat_shoe:{keywords:["ballet","slip-on","slipper"],char:"🥿",fitzpatrick_scale:false,category:"people"},high_heel:{keywords:["fashion","shoes","female","pumps","stiletto"],char:"👠",fitzpatrick_scale:false,category:"people"},sandal:{keywords:["shoes","fashion","flip flops"],char:"👡",fitzpatrick_scale:false,category:"people"},boot:{keywords:["shoes","fashion"],char:"👢",fitzpatrick_scale:false,category:"people"},mans_shoe:{keywords:["fashion","male"],char:"👞",fitzpatrick_scale:false,category:"people"},athletic_shoe:{keywords:["shoes","sports","sneakers"],char:"👟",fitzpatrick_scale:false,category:"people"},hiking_boot:{keywords:["backpacking","camping","hiking"],char:"🥾",fitzpatrick_scale:false,category:"people"},socks:{keywords:["stockings","clothes"],char:"🧦",fitzpatrick_scale:false,category:"people"},gloves:{keywords:["hands","winter","clothes"],char:"🧤",fitzpatrick_scale:false,category:"people"},scarf:{keywords:["neck","winter","clothes"],char:"🧣",fitzpatrick_scale:false,category:"people"},womans_hat:{keywords:["fashion","accessories","female","lady","spring"],char:"👒",fitzpatrick_scale:false,category:"people"},tophat:{keywords:["magic","gentleman","classy","circus"],char:"🎩",fitzpatrick_scale:false,category:"people"},billed_hat:{keywords:["cap","baseball"],char:"🧢",fitzpatrick_scale:false,category:"people"},rescue_worker_helmet:{keywords:["construction","build"],char:"⛑",fitzpatrick_scale:false,category:"people"},mortar_board:{keywords:["school","college","degree","university","graduation","cap","hat","legal","learn","education"],char:"🎓",fitzpatrick_scale:false,category:"people"},crown:{keywords:["king","kod","leader","royalty","lord"],char:"👑",fitzpatrick_scale:false,category:"people"},school_satchel:{keywords:["student","education","bag","backpack"],char:"🎒",fitzpatrick_scale:false,category:"people"},luggage:{keywords:["packing","travel"],char:"🧳",fitzpatrick_scale:false,category:"people"},pouch:{keywords:["bag","accessories","shopping"],char:"👝",fitzpatrick_scale:false,category:"people"},purse:{keywords:["fashion","accessories","money","sales","shopping"],char:"👛",fitzpatrick_scale:false,category:"people"},handbag:{keywords:["fashion","accessory","accessories","shopping"],char:"👜",fitzpatrick_scale:false,category:"people"},briefcase:{keywords:["business","documents","work","law","legal","job","career"],char:"💼",fitzpatrick_scale:false,category:"people"},eyeglasses:{keywords:["fashion","accessories","eyesight","nerdy","dork","geek"],char:"👓",fitzpatrick_scale:false,category:"people"},dark_sunglasses:{keywords:["face","cool","accessories"],char:"🕶",fitzpatrick_scale:false,category:"people"},goggles:{keywords:["eyes","protection","safety"],char:"🥽",fitzpatrick_scale:false,category:"people"},ring:{keywords:["wedding","propose","marriage","valentines","diamond","fashion","jewelry","gem","engagement"],char:"💍",fitzpatrick_scale:false,category:"people"},closed_umbrella:{keywords:["weather","rain","drizzle"],char:"🌂",fitzpatrick_scale:false,category:"people"},dog:{keywords:["animal","friend","nature","woof","puppy","pet","faithful"],char:"🐶",fitzpatrick_scale:false,category:"animals_and_nature"},cat:{keywords:["animal","meow","nature","pet","kitten"],char:"🐱",fitzpatrick_scale:false,category:"animals_and_nature"},mouse:{keywords:["animal","nature","cheese_wedge","rodent"],char:"🐭",fitzpatrick_scale:false,category:"animals_and_nature"},hamster:{keywords:["animal","nature"],char:"🐹",fitzpatrick_scale:false,category:"animals_and_nature"},rabbit:{keywords:["animal","nature","pet","spring","magic","bunny"],char:"🐰",fitzpatrick_scale:false,category:"animals_and_nature"},fox_face:{keywords:["animal","nature","face"],char:"🦊",fitzpatrick_scale:false,category:"animals_and_nature"},bear:{keywords:["animal","nature","wild"],char:"🐻",fitzpatrick_scale:false,category:"animals_and_nature"},panda_face:{keywords:["animal","nature","panda"],char:"🐼",fitzpatrick_scale:false,category:"animals_and_nature"},koala:{keywords:["animal","nature"],char:"🐨",fitzpatrick_scale:false,category:"animals_and_nature"},tiger:{keywords:["animal","cat","danger","wild","nature","roar"],char:"🐯",fitzpatrick_scale:false,category:"animals_and_nature"},lion:{keywords:["animal","nature"],char:"🦁",fitzpatrick_scale:false,category:"animals_and_nature"},cow:{keywords:["beef","ox","animal","nature","moo","milk"],char:"🐮",fitzpatrick_scale:false,category:"animals_and_nature"},pig:{keywords:["animal","oink","nature"],char:"🐷",fitzpatrick_scale:false,category:"animals_and_nature"},pig_nose:{keywords:["animal","oink"],char:"🐽",fitzpatrick_scale:false,category:"animals_and_nature"},frog:{keywords:["animal","nature","croak","toad"],char:"🐸",fitzpatrick_scale:false,category:"animals_and_nature"},squid:{keywords:["animal","nature","ocean","sea"],char:"🦑",fitzpatrick_scale:false,category:"animals_and_nature"},octopus:{keywords:["animal","creature","ocean","sea","nature","beach"],char:"🐙",fitzpatrick_scale:false,category:"animals_and_nature"},shrimp:{keywords:["animal","ocean","nature","seafood"],char:"🦐",fitzpatrick_scale:false,category:"animals_and_nature"},monkey_face:{keywords:["animal","nature","circus"],char:"🐵",fitzpatrick_scale:false,category:"animals_and_nature"},gorilla:{keywords:["animal","nature","circus"],char:"🦍",fitzpatrick_scale:false,category:"animals_and_nature"},see_no_evil:{keywords:["monkey","animal","nature","haha"],char:"🙈",fitzpatrick_scale:false,category:"animals_and_nature"},hear_no_evil:{keywords:["animal","monkey","nature"],char:"🙉",fitzpatrick_scale:false,category:"animals_and_nature"},speak_no_evil:{keywords:["monkey","animal","nature","omg"],char:"🙊",fitzpatrick_scale:false,category:"animals_and_nature"},monkey:{keywords:["animal","nature","banana","circus"],char:"🐒",fitzpatrick_scale:false,category:"animals_and_nature"},chicken:{keywords:["animal","cluck","nature","bird"],char:"🐔",fitzpatrick_scale:false,category:"animals_and_nature"},penguin:{keywords:["animal","nature"],char:"🐧",fitzpatrick_scale:false,category:"animals_and_nature"},bird:{keywords:["animal","nature","fly","tweet","spring"],char:"🐦",fitzpatrick_scale:false,category:"animals_and_nature"},baby_chick:{keywords:["animal","chicken","bird"],char:"🐤",fitzpatrick_scale:false,category:"animals_and_nature"},hatching_chick:{keywords:["animal","chicken","egg","born","baby","bird"],char:"🐣",fitzpatrick_scale:false,category:"animals_and_nature"},hatched_chick:{keywords:["animal","chicken","baby","bird"],char:"🐥",fitzpatrick_scale:false,category:"animals_and_nature"},duck:{keywords:["animal","nature","bird","mallard"],char:"🦆",fitzpatrick_scale:false,category:"animals_and_nature"},eagle:{keywords:["animal","nature","bird"],char:"🦅",fitzpatrick_scale:false,category:"animals_and_nature"},owl:{keywords:["animal","nature","bird","hoot"],char:"🦉",fitzpatrick_scale:false,category:"animals_and_nature"},bat:{keywords:["animal","nature","blind","vampire"],char:"🦇",fitzpatrick_scale:false,category:"animals_and_nature"},wolf:{keywords:["animal","nature","wild"],char:"🐺",fitzpatrick_scale:false,category:"animals_and_nature"},boar:{keywords:["animal","nature"],char:"🐗",fitzpatrick_scale:false,category:"animals_and_nature"},horse:{keywords:["animal","brown","nature"],char:"🐴",fitzpatrick_scale:false,category:"animals_and_nature"},unicorn:{keywords:["animal","nature","mystical"],char:"🦄",fitzpatrick_scale:false,category:"animals_and_nature"},honeybee:{keywords:["animal","insect","nature","bug","spring","honey"],char:"🐝",fitzpatrick_scale:false,category:"animals_and_nature"},bug:{keywords:["animal","insect","nature","worm"],char:"🐛",fitzpatrick_scale:false,category:"animals_and_nature"},butterfly:{keywords:["animal","insect","nature","caterpillar"],char:"🦋",fitzpatrick_scale:false,category:"animals_and_nature"},snail:{keywords:["slow","animal","shell"],char:"🐌",fitzpatrick_scale:false,category:"animals_and_nature"},beetle:{keywords:["animal","insect","nature","ladybug"],char:"🐞",fitzpatrick_scale:false,category:"animals_and_nature"},ant:{keywords:["animal","insect","nature","bug"],char:"🐜",fitzpatrick_scale:false,category:"animals_and_nature"},grasshopper:{keywords:["animal","cricket","chirp"],char:"🦗",fitzpatrick_scale:false,category:"animals_and_nature"},spider:{keywords:["animal","arachnid"],char:"🕷",fitzpatrick_scale:false,category:"animals_and_nature"},scorpion:{keywords:["animal","arachnid"],char:"🦂",fitzpatrick_scale:false,category:"animals_and_nature"},crab:{keywords:["animal","crustacean"],char:"🦀",fitzpatrick_scale:false,category:"animals_and_nature"},snake:{keywords:["animal","evil","nature","hiss","python"],char:"🐍",fitzpatrick_scale:false,category:"animals_and_nature"},lizard:{keywords:["animal","nature","reptile"],char:"🦎",fitzpatrick_scale:false,category:"animals_and_nature"},"t-rex":{keywords:["animal","nature","dinosaur","tyrannosaurus","extinct"],char:"🦖",fitzpatrick_scale:false,category:"animals_and_nature"},sauropod:{keywords:["animal","nature","dinosaur","brachiosaurus","brontosaurus","diplodocus","extinct"],char:"🦕",fitzpatrick_scale:false,category:"animals_and_nature"},turtle:{keywords:["animal","slow","nature","tortoise"],char:"🐢",fitzpatrick_scale:false,category:"animals_and_nature"},tropical_fish:{keywords:["animal","swim","ocean","beach","nemo"],char:"🐠",fitzpatrick_scale:false,category:"animals_and_nature"},fish:{keywords:["animal","food","nature"],char:"🐟",fitzpatrick_scale:false,category:"animals_and_nature"},blowfish:{keywords:["animal","nature","food","sea","ocean"],char:"🐡",fitzpatrick_scale:false,category:"animals_and_nature"},dolphin:{keywords:["animal","nature","fish","sea","ocean","flipper","fins","beach"],char:"🐬",fitzpatrick_scale:false,category:"animals_and_nature"},shark:{keywords:["animal","nature","fish","sea","ocean","jaws","fins","beach"],char:"🦈",fitzpatrick_scale:false,category:"animals_and_nature"},whale:{keywords:["animal","nature","sea","ocean"],char:"🐳",fitzpatrick_scale:false,category:"animals_and_nature"},whale2:{keywords:["animal","nature","sea","ocean"],char:"🐋",fitzpatrick_scale:false,category:"animals_and_nature"},crocodile:{keywords:["animal","nature","reptile","lizard","alligator"],char:"🐊",fitzpatrick_scale:false,category:"animals_and_nature"},leopard:{keywords:["animal","nature"],char:"🐆",fitzpatrick_scale:false,category:"animals_and_nature"},zebra:{keywords:["animal","nature","stripes","safari"],char:"🦓",fitzpatrick_scale:false,category:"animals_and_nature"},tiger2:{keywords:["animal","nature","roar"],char:"🐅",fitzpatrick_scale:false,category:"animals_and_nature"},water_buffalo:{keywords:["animal","nature","ox","cow"],char:"🐃",fitzpatrick_scale:false,category:"animals_and_nature"},ox:{keywords:["animal","cow","beef"],char:"🐂",fitzpatrick_scale:false,category:"animals_and_nature"},cow2:{keywords:["beef","ox","animal","nature","moo","milk"],char:"🐄",fitzpatrick_scale:false,category:"animals_and_nature"},deer:{keywords:["animal","nature","horns","venison"],char:"🦌",fitzpatrick_scale:false,category:"animals_and_nature"},dromedary_camel:{keywords:["animal","hot","desert","hump"],char:"🐪",fitzpatrick_scale:false,category:"animals_and_nature"},camel:{keywords:["animal","nature","hot","desert","hump"],char:"🐫",fitzpatrick_scale:false,category:"animals_and_nature"},giraffe:{keywords:["animal","nature","spots","safari"],char:"🦒",fitzpatrick_scale:false,category:"animals_and_nature"},elephant:{keywords:["animal","nature","nose","th","circus"],char:"🐘",fitzpatrick_scale:false,category:"animals_and_nature"},rhinoceros:{keywords:["animal","nature","horn"],char:"🦏",fitzpatrick_scale:false,category:"animals_and_nature"},goat:{keywords:["animal","nature"],char:"🐐",fitzpatrick_scale:false,category:"animals_and_nature"},ram:{keywords:["animal","sheep","nature"],char:"🐏",fitzpatrick_scale:false,category:"animals_and_nature"},sheep:{keywords:["animal","nature","wool","shipit"],char:"🐑",fitzpatrick_scale:false,category:"animals_and_nature"},racehorse:{keywords:["animal","gamble","luck"],char:"🐎",fitzpatrick_scale:false,category:"animals_and_nature"},pig2:{keywords:["animal","nature"],char:"🐖",fitzpatrick_scale:false,category:"animals_and_nature"},rat:{keywords:["animal","mouse","rodent"],char:"🐀",fitzpatrick_scale:false,category:"animals_and_nature"},mouse2:{keywords:["animal","nature","rodent"],char:"🐁",fitzpatrick_scale:false,category:"animals_and_nature"},rooster:{keywords:["animal","nature","chicken"],char:"🐓",fitzpatrick_scale:false,category:"animals_and_nature"},turkey:{keywords:["animal","bird"],char:"🦃",fitzpatrick_scale:false,category:"animals_and_nature"},dove:{keywords:["animal","bird"],char:"🕊",fitzpatrick_scale:false,category:"animals_and_nature"},dog2:{keywords:["animal","nature","friend","doge","pet","faithful"],char:"🐕",fitzpatrick_scale:false,category:"animals_and_nature"},poodle:{keywords:["dog","animal","101","nature","pet"],char:"🐩",fitzpatrick_scale:false,category:"animals_and_nature"},cat2:{keywords:["animal","meow","pet","cats"],char:"🐈",fitzpatrick_scale:false,category:"animals_and_nature"},rabbit2:{keywords:["animal","nature","pet","magic","spring"],char:"🐇",fitzpatrick_scale:false,category:"animals_and_nature"},chipmunk:{keywords:["animal","nature","rodent","squirrel"],char:"🐿",fitzpatrick_scale:false,category:"animals_and_nature"},hedgehog:{keywords:["animal","nature","spiny"],char:"🦔",fitzpatrick_scale:false,category:"animals_and_nature"},raccoon:{keywords:["animal","nature"],char:"🦝",fitzpatrick_scale:false,category:"animals_and_nature"},llama:{keywords:["animal","nature","alpaca"],char:"🦙",fitzpatrick_scale:false,category:"animals_and_nature"},hippopotamus:{keywords:["animal","nature"],char:"🦛",fitzpatrick_scale:false,category:"animals_and_nature"},kangaroo:{keywords:["animal","nature","australia","joey","hop","marsupial"],char:"🦘",fitzpatrick_scale:false,category:"animals_and_nature"},badger:{keywords:["animal","nature","honey"],char:"🦡",fitzpatrick_scale:false,category:"animals_and_nature"},swan:{keywords:["animal","nature","bird"],char:"🦢",fitzpatrick_scale:false,category:"animals_and_nature"},peacock:{keywords:["animal","nature","peahen","bird"],char:"🦚",fitzpatrick_scale:false,category:"animals_and_nature"},parrot:{keywords:["animal","nature","bird","pirate","talk"],char:"🦜",fitzpatrick_scale:false,category:"animals_and_nature"},lobster:{keywords:["animal","nature","bisque","claws","seafood"],char:"🦞",fitzpatrick_scale:false,category:"animals_and_nature"},mosquito:{keywords:["animal","nature","insect","malaria"],char:"🦟",fitzpatrick_scale:false,category:"animals_and_nature"},paw_prints:{keywords:["animal","tracking","footprints","dog","cat","pet","feet"],char:"🐾",fitzpatrick_scale:false,category:"animals_and_nature"},dragon:{keywords:["animal","myth","nature","chinese","green"],char:"🐉",fitzpatrick_scale:false,category:"animals_and_nature"},dragon_face:{keywords:["animal","myth","nature","chinese","green"],char:"🐲",fitzpatrick_scale:false,category:"animals_and_nature"},cactus:{keywords:["vegetable","plant","nature"],char:"🌵",fitzpatrick_scale:false,category:"animals_and_nature"},christmas_tree:{keywords:["festival","vacation","december","xmas","celebration"],char:"🎄",fitzpatrick_scale:false,category:"animals_and_nature"},evergreen_tree:{keywords:["plant","nature"],char:"🌲",fitzpatrick_scale:false,category:"animals_and_nature"},deciduous_tree:{keywords:["plant","nature"],char:"🌳",fitzpatrick_scale:false,category:"animals_and_nature"},palm_tree:{keywords:["plant","vegetable","nature","summer","beach","mojito","tropical"],char:"🌴",fitzpatrick_scale:false,category:"animals_and_nature"},seedling:{keywords:["plant","nature","grass","lawn","spring"],char:"🌱",fitzpatrick_scale:false,category:"animals_and_nature"},herb:{keywords:["vegetable","plant","medicine","weed","grass","lawn"],char:"🌿",fitzpatrick_scale:false,category:"animals_and_nature"},shamrock:{keywords:["vegetable","plant","nature","irish","clover"],char:"☘",fitzpatrick_scale:false,category:"animals_and_nature"},four_leaf_clover:{keywords:["vegetable","plant","nature","lucky","irish"],char:"🍀",fitzpatrick_scale:false,category:"animals_and_nature"},bamboo:{keywords:["plant","nature","vegetable","panda","pine_decoration"],char:"🎍",fitzpatrick_scale:false,category:"animals_and_nature"},tanabata_tree:{keywords:["plant","nature","branch","summer"],char:"🎋",fitzpatrick_scale:false,category:"animals_and_nature"},leaves:{keywords:["nature","plant","tree","vegetable","grass","lawn","spring"],char:"🍃",fitzpatrick_scale:false,category:"animals_and_nature"},fallen_leaf:{keywords:["nature","plant","vegetable","leaves"],char:"🍂",fitzpatrick_scale:false,category:"animals_and_nature"},maple_leaf:{keywords:["nature","plant","vegetable","ca","fall"],char:"🍁",fitzpatrick_scale:false,category:"animals_and_nature"},ear_of_rice:{keywords:["nature","plant"],char:"🌾",fitzpatrick_scale:false,category:"animals_and_nature"},hibiscus:{keywords:["plant","vegetable","flowers","beach"],char:"🌺",fitzpatrick_scale:false,category:"animals_and_nature"},sunflower:{keywords:["nature","plant","fall"],char:"🌻",fitzpatrick_scale:false,category:"animals_and_nature"},rose:{keywords:["flowers","valentines","love","spring"],char:"🌹",fitzpatrick_scale:false,category:"animals_and_nature"},wilted_flower:{keywords:["plant","nature","flower"],char:"🥀",fitzpatrick_scale:false,category:"animals_and_nature"},tulip:{keywords:["flowers","plant","nature","summer","spring"],char:"🌷",fitzpatrick_scale:false,category:"animals_and_nature"},blossom:{keywords:["nature","flowers","yellow"],char:"🌼",fitzpatrick_scale:false,category:"animals_and_nature"},cherry_blossom:{keywords:["nature","plant","spring","flower"],char:"🌸",fitzpatrick_scale:false,category:"animals_and_nature"},bouquet:{keywords:["flowers","nature","spring"],char:"💐",fitzpatrick_scale:false,category:"animals_and_nature"},mushroom:{keywords:["plant","vegetable"],char:"🍄",fitzpatrick_scale:false,category:"animals_and_nature"},chestnut:{keywords:["food","squirrel"],char:"🌰",fitzpatrick_scale:false,category:"animals_and_nature"},jack_o_lantern:{keywords:["halloween","light","pumpkin","creepy","fall"],char:"🎃",fitzpatrick_scale:false,category:"animals_and_nature"},shell:{keywords:["nature","sea","beach"],char:"🐚",fitzpatrick_scale:false,category:"animals_and_nature"},spider_web:{keywords:["animal","insect","arachnid","silk"],char:"🕸",fitzpatrick_scale:false,category:"animals_and_nature"},earth_americas:{keywords:["globe","world","USA","international"],char:"🌎",fitzpatrick_scale:false,category:"animals_and_nature"},earth_africa:{keywords:["globe","world","international"],char:"🌍",fitzpatrick_scale:false,category:"animals_and_nature"},earth_asia:{keywords:["globe","world","east","international"],char:"🌏",fitzpatrick_scale:false,category:"animals_and_nature"},full_moon:{keywords:["nature","yellow","twilight","planet","space","night","evening","sleep"],char:"🌕",fitzpatrick_scale:false,category:"animals_and_nature"},waning_gibbous_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep","waxing_gibbous_moon"],char:"🌖",fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌗",fitzpatrick_scale:false,category:"animals_and_nature"},waning_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌘",fitzpatrick_scale:false,category:"animals_and_nature"},new_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌑",fitzpatrick_scale:false,category:"animals_and_nature"},waxing_crescent_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌒",fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌓",fitzpatrick_scale:false,category:"animals_and_nature"},waxing_gibbous_moon:{keywords:["nature","night","sky","gray","twilight","planet","space","evening","sleep"],char:"🌔",fitzpatrick_scale:false,category:"animals_and_nature"},new_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌚",fitzpatrick_scale:false,category:"animals_and_nature"},full_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌝",fitzpatrick_scale:false,category:"animals_and_nature"},first_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌛",fitzpatrick_scale:false,category:"animals_and_nature"},last_quarter_moon_with_face:{keywords:["nature","twilight","planet","space","night","evening","sleep"],char:"🌜",fitzpatrick_scale:false,category:"animals_and_nature"},sun_with_face:{keywords:["nature","morning","sky"],char:"🌞",fitzpatrick_scale:false,category:"animals_and_nature"},crescent_moon:{keywords:["night","sleep","sky","evening","magic"],char:"🌙",fitzpatrick_scale:false,category:"animals_and_nature"},star:{keywords:["night","yellow"],char:"⭐",fitzpatrick_scale:false,category:"animals_and_nature"},star2:{keywords:["night","sparkle","awesome","good","magic"],char:"🌟",fitzpatrick_scale:false,category:"animals_and_nature"},dizzy:{keywords:["star","sparkle","shoot","magic"],char:"💫",fitzpatrick_scale:false,category:"animals_and_nature"},sparkles:{keywords:["stars","shine","shiny","cool","awesome","good","magic"],char:"✨",fitzpatrick_scale:false,category:"animals_and_nature"},comet:{keywords:["space"],char:"☄",fitzpatrick_scale:false,category:"animals_and_nature"},sunny:{keywords:["weather","nature","brightness","summer","beach","spring"],char:"☀️",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_small_cloud:{keywords:["weather"],char:"🌤",fitzpatrick_scale:false,category:"animals_and_nature"},partly_sunny:{keywords:["weather","nature","cloudy","morning","fall","spring"],char:"⛅",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_large_cloud:{keywords:["weather"],char:"🌥",fitzpatrick_scale:false,category:"animals_and_nature"},sun_behind_rain_cloud:{keywords:["weather"],char:"🌦",fitzpatrick_scale:false,category:"animals_and_nature"},cloud:{keywords:["weather","sky"],char:"☁️",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_rain:{keywords:["weather"],char:"🌧",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning_and_rain:{keywords:["weather","lightning"],char:"⛈",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_lightning:{keywords:["weather","thunder"],char:"🌩",fitzpatrick_scale:false,category:"animals_and_nature"},zap:{keywords:["thunder","weather","lightning bolt","fast"],char:"⚡",fitzpatrick_scale:false,category:"animals_and_nature"},fire:{keywords:["hot","cook","flame"],char:"🔥",fitzpatrick_scale:false,category:"animals_and_nature"},boom:{keywords:["bomb","explode","explosion","collision","blown"],char:"💥",fitzpatrick_scale:false,category:"animals_and_nature"},snowflake:{keywords:["winter","season","cold","weather","christmas","xmas"],char:"❄️",fitzpatrick_scale:false,category:"animals_and_nature"},cloud_with_snow:{keywords:["weather"],char:"🌨",fitzpatrick_scale:false,category:"animals_and_nature"},snowman:{keywords:["winter","season","cold","weather","christmas","xmas","frozen","without_snow"],char:"⛄",fitzpatrick_scale:false,category:"animals_and_nature"},snowman_with_snow:{keywords:["winter","season","cold","weather","christmas","xmas","frozen"],char:"☃",fitzpatrick_scale:false,category:"animals_and_nature"},wind_face:{keywords:["gust","air"],char:"🌬",fitzpatrick_scale:false,category:"animals_and_nature"},dash:{keywords:["wind","air","fast","shoo","fart","smoke","puff"],char:"💨",fitzpatrick_scale:false,category:"animals_and_nature"},tornado:{keywords:["weather","cyclone","twister"],char:"🌪",fitzpatrick_scale:false,category:"animals_and_nature"},fog:{keywords:["weather"],char:"🌫",fitzpatrick_scale:false,category:"animals_and_nature"},open_umbrella:{keywords:["weather","spring"],char:"☂",fitzpatrick_scale:false,category:"animals_and_nature"},umbrella:{keywords:["rainy","weather","spring"],char:"☔",fitzpatrick_scale:false,category:"animals_and_nature"},droplet:{keywords:["water","drip","faucet","spring"],char:"💧",fitzpatrick_scale:false,category:"animals_and_nature"},sweat_drops:{keywords:["water","drip","oops"],char:"💦",fitzpatrick_scale:false,category:"animals_and_nature"},ocean:{keywords:["sea","water","wave","nature","tsunami","disaster"],char:"🌊",fitzpatrick_scale:false,category:"animals_and_nature"},green_apple:{keywords:["fruit","nature"],char:"🍏",fitzpatrick_scale:false,category:"food_and_drink"},apple:{keywords:["fruit","mac","school"],char:"🍎",fitzpatrick_scale:false,category:"food_and_drink"},pear:{keywords:["fruit","nature","food"],char:"🍐",fitzpatrick_scale:false,category:"food_and_drink"},tangerine:{keywords:["food","fruit","nature","orange"],char:"🍊",fitzpatrick_scale:false,category:"food_and_drink"},lemon:{keywords:["fruit","nature"],char:"🍋",fitzpatrick_scale:false,category:"food_and_drink"},banana:{keywords:["fruit","food","monkey"],char:"🍌",fitzpatrick_scale:false,category:"food_and_drink"},watermelon:{keywords:["fruit","food","picnic","summer"],char:"🍉",fitzpatrick_scale:false,category:"food_and_drink"},grapes:{keywords:["fruit","food","wine"],char:"🍇",fitzpatrick_scale:false,category:"food_and_drink"},strawberry:{keywords:["fruit","food","nature"],char:"🍓",fitzpatrick_scale:false,category:"food_and_drink"},melon:{keywords:["fruit","nature","food"],char:"🍈",fitzpatrick_scale:false,category:"food_and_drink"},cherries:{keywords:["food","fruit"],char:"🍒",fitzpatrick_scale:false,category:"food_and_drink"},peach:{keywords:["fruit","nature","food"],char:"🍑",fitzpatrick_scale:false,category:"food_and_drink"},pineapple:{keywords:["fruit","nature","food"],char:"🍍",fitzpatrick_scale:false,category:"food_and_drink"},coconut:{keywords:["fruit","nature","food","palm"],char:"🥥",fitzpatrick_scale:false,category:"food_and_drink"},kiwi_fruit:{keywords:["fruit","food"],char:"🥝",fitzpatrick_scale:false,category:"food_and_drink"},mango:{keywords:["fruit","food","tropical"],char:"🥭",fitzpatrick_scale:false,category:"food_and_drink"},avocado:{keywords:["fruit","food"],char:"🥑",fitzpatrick_scale:false,category:"food_and_drink"},broccoli:{keywords:["fruit","food","vegetable"],char:"🥦",fitzpatrick_scale:false,category:"food_and_drink"},tomato:{keywords:["fruit","vegetable","nature","food"],char:"🍅",fitzpatrick_scale:false,category:"food_and_drink"},eggplant:{keywords:["vegetable","nature","food","aubergine"],char:"🍆",fitzpatrick_scale:false,category:"food_and_drink"},cucumber:{keywords:["fruit","food","pickle"],char:"🥒",fitzpatrick_scale:false,category:"food_and_drink"},carrot:{keywords:["vegetable","food","orange"],char:"🥕",fitzpatrick_scale:false,category:"food_and_drink"},hot_pepper:{keywords:["food","spicy","chilli","chili"],char:"🌶",fitzpatrick_scale:false,category:"food_and_drink"},potato:{keywords:["food","tuber","vegatable","starch"],char:"🥔",fitzpatrick_scale:false,category:"food_and_drink"},corn:{keywords:["food","vegetable","plant"],char:"🌽",fitzpatrick_scale:false,category:"food_and_drink"},leafy_greens:{keywords:["food","vegetable","plant","bok choy","cabbage","kale","lettuce"],char:"🥬",fitzpatrick_scale:false,category:"food_and_drink"},sweet_potato:{keywords:["food","nature"],char:"🍠",fitzpatrick_scale:false,category:"food_and_drink"},peanuts:{keywords:["food","nut"],char:"🥜",fitzpatrick_scale:false,category:"food_and_drink"},honey_pot:{keywords:["bees","sweet","kitchen"],char:"🍯",fitzpatrick_scale:false,category:"food_and_drink"},croissant:{keywords:["food","bread","french"],char:"🥐",fitzpatrick_scale:false,category:"food_and_drink"},bread:{keywords:["food","wheat","breakfast","toast"],char:"🍞",fitzpatrick_scale:false,category:"food_and_drink"},baguette_bread:{keywords:["food","bread","french"],char:"🥖",fitzpatrick_scale:false,category:"food_and_drink"},bagel:{keywords:["food","bread","bakery","schmear"],char:"🥯",fitzpatrick_scale:false,category:"food_and_drink"},pretzel:{keywords:["food","bread","twisted"],char:"🥨",fitzpatrick_scale:false,category:"food_and_drink"},cheese:{keywords:["food","chadder"],char:"🧀",fitzpatrick_scale:false,category:"food_and_drink"},egg:{keywords:["food","chicken","breakfast"],char:"🥚",fitzpatrick_scale:false,category:"food_and_drink"},bacon:{keywords:["food","breakfast","pork","pig","meat"],char:"🥓",fitzpatrick_scale:false,category:"food_and_drink"},steak:{keywords:["food","cow","meat","cut","chop","lambchop","porkchop"],char:"🥩",fitzpatrick_scale:false,category:"food_and_drink"},pancakes:{keywords:["food","breakfast","flapjacks","hotcakes"],char:"🥞",fitzpatrick_scale:false,category:"food_and_drink"},poultry_leg:{keywords:["food","meat","drumstick","bird","chicken","turkey"],char:"🍗",fitzpatrick_scale:false,category:"food_and_drink"},meat_on_bone:{keywords:["good","food","drumstick"],char:"🍖",fitzpatrick_scale:false,category:"food_and_drink"},bone:{keywords:["skeleton"],char:"🦴",fitzpatrick_scale:false,category:"food_and_drink"},fried_shrimp:{keywords:["food","animal","appetizer","summer"],char:"🍤",fitzpatrick_scale:false,category:"food_and_drink"},fried_egg:{keywords:["food","breakfast","kitchen","egg"],char:"🍳",fitzpatrick_scale:false,category:"food_and_drink"},hamburger:{keywords:["meat","fast food","beef","cheeseburger","mcdonalds","burger king"],char:"🍔",fitzpatrick_scale:false,category:"food_and_drink"},fries:{keywords:["chips","snack","fast food"],char:"🍟",fitzpatrick_scale:false,category:"food_and_drink"},stuffed_flatbread:{keywords:["food","flatbread","stuffed","gyro"],char:"🥙",fitzpatrick_scale:false,category:"food_and_drink"},hotdog:{keywords:["food","frankfurter"],char:"🌭",fitzpatrick_scale:false,category:"food_and_drink"},pizza:{keywords:["food","party"],char:"🍕",fitzpatrick_scale:false,category:"food_and_drink"},sandwich:{keywords:["food","lunch","bread"],char:"🥪",fitzpatrick_scale:false,category:"food_and_drink"},canned_food:{keywords:["food","soup"],char:"🥫",fitzpatrick_scale:false,category:"food_and_drink"},spaghetti:{keywords:["food","italian","noodle"],char:"🍝",fitzpatrick_scale:false,category:"food_and_drink"},taco:{keywords:["food","mexican"],char:"🌮",fitzpatrick_scale:false,category:"food_and_drink"},burrito:{keywords:["food","mexican"],char:"🌯",fitzpatrick_scale:false,category:"food_and_drink"},green_salad:{keywords:["food","healthy","lettuce"],char:"🥗",fitzpatrick_scale:false,category:"food_and_drink"},shallow_pan_of_food:{keywords:["food","cooking","casserole","paella"],char:"🥘",fitzpatrick_scale:false,category:"food_and_drink"},ramen:{keywords:["food","japanese","noodle","chopsticks"],char:"🍜",fitzpatrick_scale:false,category:"food_and_drink"},stew:{keywords:["food","meat","soup"],char:"🍲",fitzpatrick_scale:false,category:"food_and_drink"},fish_cake:{keywords:["food","japan","sea","beach","narutomaki","pink","swirl","kamaboko","surimi","ramen"],char:"🍥",fitzpatrick_scale:false,category:"food_and_drink"},fortune_cookie:{keywords:["food","prophecy"],char:"🥠",fitzpatrick_scale:false,category:"food_and_drink"},sushi:{keywords:["food","fish","japanese","rice"],char:"🍣",fitzpatrick_scale:false,category:"food_and_drink"},bento:{keywords:["food","japanese","box"],char:"🍱",fitzpatrick_scale:false,category:"food_and_drink"},curry:{keywords:["food","spicy","hot","indian"],char:"🍛",fitzpatrick_scale:false,category:"food_and_drink"},rice_ball:{keywords:["food","japanese"],char:"🍙",fitzpatrick_scale:false,category:"food_and_drink"},rice:{keywords:["food","china","asian"],char:"🍚",fitzpatrick_scale:false,category:"food_and_drink"},rice_cracker:{keywords:["food","japanese"],char:"🍘",fitzpatrick_scale:false,category:"food_and_drink"},oden:{keywords:["food","japanese"],char:"🍢",fitzpatrick_scale:false,category:"food_and_drink"},dango:{keywords:["food","dessert","sweet","japanese","barbecue","meat"],char:"🍡",fitzpatrick_scale:false,category:"food_and_drink"},shaved_ice:{keywords:["hot","dessert","summer"],char:"🍧",fitzpatrick_scale:false,category:"food_and_drink"},ice_cream:{keywords:["food","hot","dessert"],char:"🍨",fitzpatrick_scale:false,category:"food_and_drink"},icecream:{keywords:["food","hot","dessert","summer"],char:"🍦",fitzpatrick_scale:false,category:"food_and_drink"},pie:{keywords:["food","dessert","pastry"],char:"🥧",fitzpatrick_scale:false,category:"food_and_drink"},cake:{keywords:["food","dessert"],char:"🍰",fitzpatrick_scale:false,category:"food_and_drink"},cupcake:{keywords:["food","dessert","bakery","sweet"],char:"🧁",fitzpatrick_scale:false,category:"food_and_drink"},moon_cake:{keywords:["food","autumn"],char:"🥮",fitzpatrick_scale:false,category:"food_and_drink"},birthday:{keywords:["food","dessert","cake"],char:"🎂",fitzpatrick_scale:false,category:"food_and_drink"},custard:{keywords:["dessert","food"],char:"🍮",fitzpatrick_scale:false,category:"food_and_drink"},candy:{keywords:["snack","dessert","sweet","lolly"],char:"🍬",fitzpatrick_scale:false,category:"food_and_drink"},lollipop:{keywords:["food","snack","candy","sweet"],char:"🍭",fitzpatrick_scale:false,category:"food_and_drink"},chocolate_bar:{keywords:["food","snack","dessert","sweet"],char:"🍫",fitzpatrick_scale:false,category:"food_and_drink"},popcorn:{keywords:["food","movie theater","films","snack"],char:"🍿",fitzpatrick_scale:false,category:"food_and_drink"},dumpling:{keywords:["food","empanada","pierogi","potsticker"],char:"🥟",fitzpatrick_scale:false,category:"food_and_drink"},doughnut:{keywords:["food","dessert","snack","sweet","donut"],char:"🍩",fitzpatrick_scale:false,category:"food_and_drink"},cookie:{keywords:["food","snack","oreo","chocolate","sweet","dessert"],char:"🍪",fitzpatrick_scale:false,category:"food_and_drink"},milk_glass:{keywords:["beverage","drink","cow"],char:"🥛",fitzpatrick_scale:false,category:"food_and_drink"},beer:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"🍺",fitzpatrick_scale:false,category:"food_and_drink"},beers:{keywords:["relax","beverage","drink","drunk","party","pub","summer","alcohol","booze"],char:"🍻",fitzpatrick_scale:false,category:"food_and_drink"},clinking_glasses:{keywords:["beverage","drink","party","alcohol","celebrate","cheers","wine","champagne","toast"],char:"🥂",fitzpatrick_scale:false,category:"food_and_drink"},wine_glass:{keywords:["drink","beverage","drunk","alcohol","booze"],char:"🍷",fitzpatrick_scale:false,category:"food_and_drink"},tumbler_glass:{keywords:["drink","beverage","drunk","alcohol","liquor","booze","bourbon","scotch","whisky","glass","shot"],char:"🥃",fitzpatrick_scale:false,category:"food_and_drink"},cocktail:{keywords:["drink","drunk","alcohol","beverage","booze","mojito"],char:"🍸",fitzpatrick_scale:false,category:"food_and_drink"},tropical_drink:{keywords:["beverage","cocktail","summer","beach","alcohol","booze","mojito"],char:"🍹",fitzpatrick_scale:false,category:"food_and_drink"},champagne:{keywords:["drink","wine","bottle","celebration"],char:"🍾",fitzpatrick_scale:false,category:"food_and_drink"},sake:{keywords:["wine","drink","drunk","beverage","japanese","alcohol","booze"],char:"🍶",fitzpatrick_scale:false,category:"food_and_drink"},tea:{keywords:["drink","bowl","breakfast","green","british"],char:"🍵",fitzpatrick_scale:false,category:"food_and_drink"},cup_with_straw:{keywords:["drink","soda"],char:"🥤",fitzpatrick_scale:false,category:"food_and_drink"},coffee:{keywords:["beverage","caffeine","latte","espresso"],char:"☕",fitzpatrick_scale:false,category:"food_and_drink"},baby_bottle:{keywords:["food","container","milk"],char:"🍼",fitzpatrick_scale:false,category:"food_and_drink"},salt:{keywords:["condiment","shaker"],char:"🧂",fitzpatrick_scale:false,category:"food_and_drink"},spoon:{keywords:["cutlery","kitchen","tableware"],char:"🥄",fitzpatrick_scale:false,category:"food_and_drink"},fork_and_knife:{keywords:["cutlery","kitchen"],char:"🍴",fitzpatrick_scale:false,category:"food_and_drink"},plate_with_cutlery:{keywords:["food","eat","meal","lunch","dinner","restaurant"],char:"🍽",fitzpatrick_scale:false,category:"food_and_drink"},bowl_with_spoon:{keywords:["food","breakfast","cereal","oatmeal","porridge"],char:"🥣",fitzpatrick_scale:false,category:"food_and_drink"},takeout_box:{keywords:["food","leftovers"],char:"🥡",fitzpatrick_scale:false,category:"food_and_drink"},chopsticks:{keywords:["food"],char:"🥢",fitzpatrick_scale:false,category:"food_and_drink"},soccer:{keywords:["sports","football"],char:"⚽",fitzpatrick_scale:false,category:"activity"},basketball:{keywords:["sports","balls","NBA"],char:"🏀",fitzpatrick_scale:false,category:"activity"},football:{keywords:["sports","balls","NFL"],char:"🏈",fitzpatrick_scale:false,category:"activity"},baseball:{keywords:["sports","balls"],char:"⚾",fitzpatrick_scale:false,category:"activity"},softball:{keywords:["sports","balls"],char:"🥎",fitzpatrick_scale:false,category:"activity"},tennis:{keywords:["sports","balls","green"],char:"🎾",fitzpatrick_scale:false,category:"activity"},volleyball:{keywords:["sports","balls"],char:"🏐",fitzpatrick_scale:false,category:"activity"},rugby_football:{keywords:["sports","team"],char:"🏉",fitzpatrick_scale:false,category:"activity"},flying_disc:{keywords:["sports","frisbee","ultimate"],char:"🥏",fitzpatrick_scale:false,category:"activity"},"8ball":{keywords:["pool","hobby","game","luck","magic"],char:"🎱",fitzpatrick_scale:false,category:"activity"},golf:{keywords:["sports","business","flag","hole","summer"],char:"⛳",fitzpatrick_scale:false,category:"activity"},golfing_woman:{keywords:["sports","business","woman","female"],char:"🏌️‍♀️",fitzpatrick_scale:false,category:"activity"},golfing_man:{keywords:["sports","business"],char:"🏌",fitzpatrick_scale:true,category:"activity"},ping_pong:{keywords:["sports","pingpong"],char:"🏓",fitzpatrick_scale:false,category:"activity"},badminton:{keywords:["sports"],char:"🏸",fitzpatrick_scale:false,category:"activity"},goal_net:{keywords:["sports"],char:"🥅",fitzpatrick_scale:false,category:"activity"},ice_hockey:{keywords:["sports"],char:"🏒",fitzpatrick_scale:false,category:"activity"},field_hockey:{keywords:["sports"],char:"🏑",fitzpatrick_scale:false,category:"activity"},lacrosse:{keywords:["sports","ball","stick"],char:"🥍",fitzpatrick_scale:false,category:"activity"},cricket:{keywords:["sports"],char:"🏏",fitzpatrick_scale:false,category:"activity"},ski:{keywords:["sports","winter","cold","snow"],char:"🎿",fitzpatrick_scale:false,category:"activity"},skier:{keywords:["sports","winter","snow"],char:"⛷",fitzpatrick_scale:false,category:"activity"},snowboarder:{keywords:["sports","winter"],char:"🏂",fitzpatrick_scale:true,category:"activity"},person_fencing:{keywords:["sports","fencing","sword"],char:"🤺",fitzpatrick_scale:false,category:"activity"},women_wrestling:{keywords:["sports","wrestlers"],char:"🤼‍♀️",fitzpatrick_scale:false,category:"activity"},men_wrestling:{keywords:["sports","wrestlers"],char:"🤼‍♂️",fitzpatrick_scale:false,category:"activity"},woman_cartwheeling:{keywords:["gymnastics"],char:"🤸‍♀️",fitzpatrick_scale:true,category:"activity"},man_cartwheeling:{keywords:["gymnastics"],char:"🤸‍♂️",fitzpatrick_scale:true,category:"activity"},woman_playing_handball:{keywords:["sports"],char:"🤾‍♀️",fitzpatrick_scale:true,category:"activity"},man_playing_handball:{keywords:["sports"],char:"🤾‍♂️",fitzpatrick_scale:true,category:"activity"},ice_skate:{keywords:["sports"],char:"⛸",fitzpatrick_scale:false,category:"activity"},curling_stone:{keywords:["sports"],char:"🥌",fitzpatrick_scale:false,category:"activity"},skateboard:{keywords:["board"],char:"🛹",fitzpatrick_scale:false,category:"activity"},sled:{keywords:["sleigh","luge","toboggan"],char:"🛷",fitzpatrick_scale:false,category:"activity"},bow_and_arrow:{keywords:["sports"],char:"🏹",fitzpatrick_scale:false,category:"activity"},fishing_pole_and_fish:{keywords:["food","hobby","summer"],char:"🎣",fitzpatrick_scale:false,category:"activity"},boxing_glove:{keywords:["sports","fighting"],char:"🥊",fitzpatrick_scale:false,category:"activity"},martial_arts_uniform:{keywords:["judo","karate","taekwondo"],char:"🥋",fitzpatrick_scale:false,category:"activity"},rowing_woman:{keywords:["sports","hobby","water","ship","woman","female"],char:"🚣‍♀️",fitzpatrick_scale:true,category:"activity"},rowing_man:{keywords:["sports","hobby","water","ship"],char:"🚣",fitzpatrick_scale:true,category:"activity"},climbing_woman:{keywords:["sports","hobby","woman","female","rock"],char:"🧗‍♀️",fitzpatrick_scale:true,category:"activity"},climbing_man:{keywords:["sports","hobby","man","male","rock"],char:"🧗‍♂️",fitzpatrick_scale:true,category:"activity"},swimming_woman:{keywords:["sports","exercise","human","athlete","water","summer","woman","female"],char:"🏊‍♀️",fitzpatrick_scale:true,category:"activity"},swimming_man:{keywords:["sports","exercise","human","athlete","water","summer"],char:"🏊",fitzpatrick_scale:true,category:"activity"},woman_playing_water_polo:{keywords:["sports","pool"],char:"🤽‍♀️",fitzpatrick_scale:true,category:"activity"},man_playing_water_polo:{keywords:["sports","pool"],char:"🤽‍♂️",fitzpatrick_scale:true,category:"activity"},woman_in_lotus_position:{keywords:["woman","female","meditation","yoga","serenity","zen","mindfulness"],char:"🧘‍♀️",fitzpatrick_scale:true,category:"activity"},man_in_lotus_position:{keywords:["man","male","meditation","yoga","serenity","zen","mindfulness"],char:"🧘‍♂️",fitzpatrick_scale:true,category:"activity"},surfing_woman:{keywords:["sports","ocean","sea","summer","beach","woman","female"],char:"🏄‍♀️",fitzpatrick_scale:true,category:"activity"},surfing_man:{keywords:["sports","ocean","sea","summer","beach"],char:"🏄",fitzpatrick_scale:true,category:"activity"},bath:{keywords:["clean","shower","bathroom"],char:"🛀",fitzpatrick_scale:true,category:"activity"},basketball_woman:{keywords:["sports","human","woman","female"],char:"⛹️‍♀️",fitzpatrick_scale:true,category:"activity"},basketball_man:{keywords:["sports","human"],char:"⛹",fitzpatrick_scale:true,category:"activity"},weight_lifting_woman:{keywords:["sports","training","exercise","woman","female"],char:"🏋️‍♀️",fitzpatrick_scale:true,category:"activity"},weight_lifting_man:{keywords:["sports","training","exercise"],char:"🏋",fitzpatrick_scale:true,category:"activity"},biking_woman:{keywords:["sports","bike","exercise","hipster","woman","female"],char:"🚴‍♀️",fitzpatrick_scale:true,category:"activity"},biking_man:{keywords:["sports","bike","exercise","hipster"],char:"🚴",fitzpatrick_scale:true,category:"activity"},mountain_biking_woman:{keywords:["transportation","sports","human","race","bike","woman","female"],char:"🚵‍♀️",fitzpatrick_scale:true,category:"activity"},mountain_biking_man:{keywords:["transportation","sports","human","race","bike"],char:"🚵",fitzpatrick_scale:true,category:"activity"},horse_racing:{keywords:["animal","betting","competition","gambling","luck"],char:"🏇",fitzpatrick_scale:true,category:"activity"},business_suit_levitating:{keywords:["suit","business","levitate","hover","jump"],char:"🕴",fitzpatrick_scale:true,category:"activity"},trophy:{keywords:["win","award","contest","place","ftw","ceremony"],char:"🏆",fitzpatrick_scale:false,category:"activity"},running_shirt_with_sash:{keywords:["play","pageant"],char:"🎽",fitzpatrick_scale:false,category:"activity"},medal_sports:{keywords:["award","winning"],char:"🏅",fitzpatrick_scale:false,category:"activity"},medal_military:{keywords:["award","winning","army"],char:"🎖",fitzpatrick_scale:false,category:"activity"},"1st_place_medal":{keywords:["award","winning","first"],char:"🥇",fitzpatrick_scale:false,category:"activity"},"2nd_place_medal":{keywords:["award","second"],char:"🥈",fitzpatrick_scale:false,category:"activity"},"3rd_place_medal":{keywords:["award","third"],char:"🥉",fitzpatrick_scale:false,category:"activity"},reminder_ribbon:{keywords:["sports","cause","support","awareness"],char:"🎗",fitzpatrick_scale:false,category:"activity"},rosette:{keywords:["flower","decoration","military"],char:"🏵",fitzpatrick_scale:false,category:"activity"},ticket:{keywords:["event","concert","pass"],char:"🎫",fitzpatrick_scale:false,category:"activity"},tickets:{keywords:["sports","concert","entrance"],char:"🎟",fitzpatrick_scale:false,category:"activity"},performing_arts:{keywords:["acting","theater","drama"],char:"🎭",fitzpatrick_scale:false,category:"activity"},art:{keywords:["design","paint","draw","colors"],char:"🎨",fitzpatrick_scale:false,category:"activity"},circus_tent:{keywords:["festival","carnival","party"],char:"🎪",fitzpatrick_scale:false,category:"activity"},woman_juggling:{keywords:["juggle","balance","skill","multitask"],char:"🤹‍♀️",fitzpatrick_scale:true,category:"activity"},man_juggling:{keywords:["juggle","balance","skill","multitask"],char:"🤹‍♂️",fitzpatrick_scale:true,category:"activity"},microphone:{keywords:["sound","music","PA","sing","talkshow"],char:"🎤",fitzpatrick_scale:false,category:"activity"},headphones:{keywords:["music","score","gadgets"],char:"🎧",fitzpatrick_scale:false,category:"activity"},musical_score:{keywords:["treble","clef","compose"],char:"🎼",fitzpatrick_scale:false,category:"activity"},musical_keyboard:{keywords:["piano","instrument","compose"],char:"🎹",fitzpatrick_scale:false,category:"activity"},drum:{keywords:["music","instrument","drumsticks","snare"],char:"🥁",fitzpatrick_scale:false,category:"activity"},saxophone:{keywords:["music","instrument","jazz","blues"],char:"🎷",fitzpatrick_scale:false,category:"activity"},trumpet:{keywords:["music","brass"],char:"🎺",fitzpatrick_scale:false,category:"activity"},guitar:{keywords:["music","instrument"],char:"🎸",fitzpatrick_scale:false,category:"activity"},violin:{keywords:["music","instrument","orchestra","symphony"],char:"🎻",fitzpatrick_scale:false,category:"activity"},clapper:{keywords:["movie","film","record"],char:"🎬",fitzpatrick_scale:false,category:"activity"},video_game:{keywords:["play","console","PS4","controller"],char:"🎮",fitzpatrick_scale:false,category:"activity"},space_invader:{keywords:["game","arcade","play"],char:"👾",fitzpatrick_scale:false,category:"activity"},dart:{keywords:["game","play","bar","target","bullseye"],char:"🎯",fitzpatrick_scale:false,category:"activity"},game_die:{keywords:["dice","random","tabletop","play","luck"],char:"🎲",fitzpatrick_scale:false,category:"activity"},chess_pawn:{keywords:["expendable"],char:"♟",fitzpatrick_scale:false,category:"activity"},slot_machine:{keywords:["bet","gamble","vegas","fruit machine","luck","casino"],char:"🎰",fitzpatrick_scale:false,category:"activity"},jigsaw:{keywords:["interlocking","puzzle","piece"],char:"🧩",fitzpatrick_scale:false,category:"activity"},bowling:{keywords:["sports","fun","play"],char:"🎳",fitzpatrick_scale:false,category:"activity"},red_car:{keywords:["red","transportation","vehicle"],char:"🚗",fitzpatrick_scale:false,category:"travel_and_places"},taxi:{keywords:["uber","vehicle","cars","transportation"],char:"🚕",fitzpatrick_scale:false,category:"travel_and_places"},blue_car:{keywords:["transportation","vehicle"],char:"🚙",fitzpatrick_scale:false,category:"travel_and_places"},bus:{keywords:["car","vehicle","transportation"],char:"🚌",fitzpatrick_scale:false,category:"travel_and_places"},trolleybus:{keywords:["bart","transportation","vehicle"],char:"🚎",fitzpatrick_scale:false,category:"travel_and_places"},racing_car:{keywords:["sports","race","fast","formula","f1"],char:"🏎",fitzpatrick_scale:false,category:"travel_and_places"},police_car:{keywords:["vehicle","cars","transportation","law","legal","enforcement"],char:"🚓",fitzpatrick_scale:false,category:"travel_and_places"},ambulance:{keywords:["health","911","hospital"],char:"🚑",fitzpatrick_scale:false,category:"travel_and_places"},fire_engine:{keywords:["transportation","cars","vehicle"],char:"🚒",fitzpatrick_scale:false,category:"travel_and_places"},minibus:{keywords:["vehicle","car","transportation"],char:"🚐",fitzpatrick_scale:false,category:"travel_and_places"},truck:{keywords:["cars","transportation"],char:"🚚",fitzpatrick_scale:false,category:"travel_and_places"},articulated_lorry:{keywords:["vehicle","cars","transportation","express"],char:"🚛",fitzpatrick_scale:false,category:"travel_and_places"},tractor:{keywords:["vehicle","car","farming","agriculture"],char:"🚜",fitzpatrick_scale:false,category:"travel_and_places"},kick_scooter:{keywords:["vehicle","kick","razor"],char:"🛴",fitzpatrick_scale:false,category:"travel_and_places"},motorcycle:{keywords:["race","sports","fast"],char:"🏍",fitzpatrick_scale:false,category:"travel_and_places"},bike:{keywords:["sports","bicycle","exercise","hipster"],char:"🚲",fitzpatrick_scale:false,category:"travel_and_places"},motor_scooter:{keywords:["vehicle","vespa","sasha"],char:"🛵",fitzpatrick_scale:false,category:"travel_and_places"},rotating_light:{keywords:["police","ambulance","911","emergency","alert","error","pinged","law","legal"],char:"🚨",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_police_car:{keywords:["vehicle","law","legal","enforcement","911"],char:"🚔",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_bus:{keywords:["vehicle","transportation"],char:"🚍",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_automobile:{keywords:["car","vehicle","transportation"],char:"🚘",fitzpatrick_scale:false,category:"travel_and_places"},oncoming_taxi:{keywords:["vehicle","cars","uber"],char:"🚖",fitzpatrick_scale:false,category:"travel_and_places"},aerial_tramway:{keywords:["transportation","vehicle","ski"],char:"🚡",fitzpatrick_scale:false,category:"travel_and_places"},mountain_cableway:{keywords:["transportation","vehicle","ski"],char:"🚠",fitzpatrick_scale:false,category:"travel_and_places"},suspension_railway:{keywords:["vehicle","transportation"],char:"🚟",fitzpatrick_scale:false,category:"travel_and_places"},railway_car:{keywords:["transportation","vehicle"],char:"🚃",fitzpatrick_scale:false,category:"travel_and_places"},train:{keywords:["transportation","vehicle","carriage","public","travel"],char:"🚋",fitzpatrick_scale:false,category:"travel_and_places"},monorail:{keywords:["transportation","vehicle"],char:"🚝",fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_side:{keywords:["transportation","vehicle"],char:"🚄",fitzpatrick_scale:false,category:"travel_and_places"},bullettrain_front:{keywords:["transportation","vehicle","speed","fast","public","travel"],char:"🚅",fitzpatrick_scale:false,category:"travel_and_places"},light_rail:{keywords:["transportation","vehicle"],char:"🚈",fitzpatrick_scale:false,category:"travel_and_places"},mountain_railway:{keywords:["transportation","vehicle"],char:"🚞",fitzpatrick_scale:false,category:"travel_and_places"},steam_locomotive:{keywords:["transportation","vehicle","train"],char:"🚂",fitzpatrick_scale:false,category:"travel_and_places"},train2:{keywords:["transportation","vehicle"],char:"🚆",fitzpatrick_scale:false,category:"travel_and_places"},metro:{keywords:["transportation","blue-square","mrt","underground","tube"],char:"🚇",fitzpatrick_scale:false,category:"travel_and_places"},tram:{keywords:["transportation","vehicle"],char:"🚊",fitzpatrick_scale:false,category:"travel_and_places"},station:{keywords:["transportation","vehicle","public"],char:"🚉",fitzpatrick_scale:false,category:"travel_and_places"},flying_saucer:{keywords:["transportation","vehicle","ufo"],char:"🛸",fitzpatrick_scale:false,category:"travel_and_places"},helicopter:{keywords:["transportation","vehicle","fly"],char:"🚁",fitzpatrick_scale:false,category:"travel_and_places"},small_airplane:{keywords:["flight","transportation","fly","vehicle"],char:"🛩",fitzpatrick_scale:false,category:"travel_and_places"},airplane:{keywords:["vehicle","transportation","flight","fly"],char:"✈️",fitzpatrick_scale:false,category:"travel_and_places"},flight_departure:{keywords:["airport","flight","landing"],char:"🛫",fitzpatrick_scale:false,category:"travel_and_places"},flight_arrival:{keywords:["airport","flight","boarding"],char:"🛬",fitzpatrick_scale:false,category:"travel_and_places"},sailboat:{keywords:["ship","summer","transportation","water","sailing"],char:"⛵",fitzpatrick_scale:false,category:"travel_and_places"},motor_boat:{keywords:["ship"],char:"🛥",fitzpatrick_scale:false,category:"travel_and_places"},speedboat:{keywords:["ship","transportation","vehicle","summer"],char:"🚤",fitzpatrick_scale:false,category:"travel_and_places"},ferry:{keywords:["boat","ship","yacht"],char:"⛴",fitzpatrick_scale:false,category:"travel_and_places"},passenger_ship:{keywords:["yacht","cruise","ferry"],char:"🛳",fitzpatrick_scale:false,category:"travel_and_places"},rocket:{keywords:["launch","ship","staffmode","NASA","outer space","outer_space","fly"],char:"🚀",fitzpatrick_scale:false,category:"travel_and_places"},artificial_satellite:{keywords:["communication","gps","orbit","spaceflight","NASA","ISS"],char:"🛰",fitzpatrick_scale:false,category:"travel_and_places"},seat:{keywords:["sit","airplane","transport","bus","flight","fly"],char:"💺",fitzpatrick_scale:false,category:"travel_and_places"},canoe:{keywords:["boat","paddle","water","ship"],char:"🛶",fitzpatrick_scale:false,category:"travel_and_places"},anchor:{keywords:["ship","ferry","sea","boat"],char:"⚓",fitzpatrick_scale:false,category:"travel_and_places"},construction:{keywords:["wip","progress","caution","warning"],char:"🚧",fitzpatrick_scale:false,category:"travel_and_places"},fuelpump:{keywords:["gas station","petroleum"],char:"⛽",fitzpatrick_scale:false,category:"travel_and_places"},busstop:{keywords:["transportation","wait"],char:"🚏",fitzpatrick_scale:false,category:"travel_and_places"},vertical_traffic_light:{keywords:["transportation","driving"],char:"🚦",fitzpatrick_scale:false,category:"travel_and_places"},traffic_light:{keywords:["transportation","signal"],char:"🚥",fitzpatrick_scale:false,category:"travel_and_places"},checkered_flag:{keywords:["contest","finishline","race","gokart"],char:"🏁",fitzpatrick_scale:false,category:"travel_and_places"},ship:{keywords:["transportation","titanic","deploy"],char:"🚢",fitzpatrick_scale:false,category:"travel_and_places"},ferris_wheel:{keywords:["photo","carnival","londoneye"],char:"🎡",fitzpatrick_scale:false,category:"travel_and_places"},roller_coaster:{keywords:["carnival","playground","photo","fun"],char:"🎢",fitzpatrick_scale:false,category:"travel_and_places"},carousel_horse:{keywords:["photo","carnival"],char:"🎠",fitzpatrick_scale:false,category:"travel_and_places"},building_construction:{keywords:["wip","working","progress"],char:"🏗",fitzpatrick_scale:false,category:"travel_and_places"},foggy:{keywords:["photo","mountain"],char:"🌁",fitzpatrick_scale:false,category:"travel_and_places"},tokyo_tower:{keywords:["photo","japanese"],char:"🗼",fitzpatrick_scale:false,category:"travel_and_places"},factory:{keywords:["building","industry","pollution","smoke"],char:"🏭",fitzpatrick_scale:false,category:"travel_and_places"},fountain:{keywords:["photo","summer","water","fresh"],char:"⛲",fitzpatrick_scale:false,category:"travel_and_places"},rice_scene:{keywords:["photo","japan","asia","tsukimi"],char:"🎑",fitzpatrick_scale:false,category:"travel_and_places"},mountain:{keywords:["photo","nature","environment"],char:"⛰",fitzpatrick_scale:false,category:"travel_and_places"},mountain_snow:{keywords:["photo","nature","environment","winter","cold"],char:"🏔",fitzpatrick_scale:false,category:"travel_and_places"},mount_fuji:{keywords:["photo","mountain","nature","japanese"],char:"🗻",fitzpatrick_scale:false,category:"travel_and_places"},volcano:{keywords:["photo","nature","disaster"],char:"🌋",fitzpatrick_scale:false,category:"travel_and_places"},japan:{keywords:["nation","country","japanese","asia"],char:"🗾",fitzpatrick_scale:false,category:"travel_and_places"},camping:{keywords:["photo","outdoors","tent"],char:"🏕",fitzpatrick_scale:false,category:"travel_and_places"},tent:{keywords:["photo","camping","outdoors"],char:"⛺",fitzpatrick_scale:false,category:"travel_and_places"},national_park:{keywords:["photo","environment","nature"],char:"🏞",fitzpatrick_scale:false,category:"travel_and_places"},motorway:{keywords:["road","cupertino","interstate","highway"],char:"🛣",fitzpatrick_scale:false,category:"travel_and_places"},railway_track:{keywords:["train","transportation"],char:"🛤",fitzpatrick_scale:false,category:"travel_and_places"},sunrise:{keywords:["morning","view","vacation","photo"],char:"🌅",fitzpatrick_scale:false,category:"travel_and_places"},sunrise_over_mountains:{keywords:["view","vacation","photo"],char:"🌄",fitzpatrick_scale:false,category:"travel_and_places"},desert:{keywords:["photo","warm","saharah"],char:"🏜",fitzpatrick_scale:false,category:"travel_and_places"},beach_umbrella:{keywords:["weather","summer","sunny","sand","mojito"],char:"🏖",fitzpatrick_scale:false,category:"travel_and_places"},desert_island:{keywords:["photo","tropical","mojito"],char:"🏝",fitzpatrick_scale:false,category:"travel_and_places"},city_sunrise:{keywords:["photo","good morning","dawn"],char:"🌇",fitzpatrick_scale:false,category:"travel_and_places"},city_sunset:{keywords:["photo","evening","sky","buildings"],char:"🌆",fitzpatrick_scale:false,category:"travel_and_places"},cityscape:{keywords:["photo","night life","urban"],char:"🏙",fitzpatrick_scale:false,category:"travel_and_places"},night_with_stars:{keywords:["evening","city","downtown"],char:"🌃",fitzpatrick_scale:false,category:"travel_and_places"},bridge_at_night:{keywords:["photo","sanfrancisco"],char:"🌉",fitzpatrick_scale:false,category:"travel_and_places"},milky_way:{keywords:["photo","space","stars"],char:"🌌",fitzpatrick_scale:false,category:"travel_and_places"},stars:{keywords:["night","photo"],char:"🌠",fitzpatrick_scale:false,category:"travel_and_places"},sparkler:{keywords:["stars","night","shine"],char:"🎇",fitzpatrick_scale:false,category:"travel_and_places"},fireworks:{keywords:["photo","festival","carnival","congratulations"],char:"🎆",fitzpatrick_scale:false,category:"travel_and_places"},rainbow:{keywords:["nature","happy","unicorn_face","photo","sky","spring"],char:"🌈",fitzpatrick_scale:false,category:"travel_and_places"},houses:{keywords:["buildings","photo"],char:"🏘",fitzpatrick_scale:false,category:"travel_and_places"},european_castle:{keywords:["building","royalty","history"],char:"🏰",fitzpatrick_scale:false,category:"travel_and_places"},japanese_castle:{keywords:["photo","building"],char:"🏯",fitzpatrick_scale:false,category:"travel_and_places"},stadium:{keywords:["photo","place","sports","concert","venue"],char:"🏟",fitzpatrick_scale:false,category:"travel_and_places"},statue_of_liberty:{keywords:["american","newyork"],char:"🗽",fitzpatrick_scale:false,category:"travel_and_places"},house:{keywords:["building","home"],char:"🏠",fitzpatrick_scale:false,category:"travel_and_places"},house_with_garden:{keywords:["home","plant","nature"],char:"🏡",fitzpatrick_scale:false,category:"travel_and_places"},derelict_house:{keywords:["abandon","evict","broken","building"],char:"🏚",fitzpatrick_scale:false,category:"travel_and_places"},office:{keywords:["building","bureau","work"],char:"🏢",fitzpatrick_scale:false,category:"travel_and_places"},department_store:{keywords:["building","shopping","mall"],char:"🏬",fitzpatrick_scale:false,category:"travel_and_places"},post_office:{keywords:["building","envelope","communication"],char:"🏣",fitzpatrick_scale:false,category:"travel_and_places"},european_post_office:{keywords:["building","email"],char:"🏤",fitzpatrick_scale:false,category:"travel_and_places"},hospital:{keywords:["building","health","surgery","doctor"],char:"🏥",fitzpatrick_scale:false,category:"travel_and_places"},bank:{keywords:["building","money","sales","cash","business","enterprise"],char:"🏦",fitzpatrick_scale:false,category:"travel_and_places"},hotel:{keywords:["building","accomodation","checkin"],char:"🏨",fitzpatrick_scale:false,category:"travel_and_places"},convenience_store:{keywords:["building","shopping","groceries"],char:"🏪",fitzpatrick_scale:false,category:"travel_and_places"},school:{keywords:["building","student","education","learn","teach"],char:"🏫",fitzpatrick_scale:false,category:"travel_and_places"},love_hotel:{keywords:["like","affection","dating"],char:"🏩",fitzpatrick_scale:false,category:"travel_and_places"},wedding:{keywords:["love","like","affection","couple","marriage","bride","groom"],char:"💒",fitzpatrick_scale:false,category:"travel_and_places"},classical_building:{keywords:["art","culture","history"],char:"🏛",fitzpatrick_scale:false,category:"travel_and_places"},church:{keywords:["building","religion","christ"],char:"⛪",fitzpatrick_scale:false,category:"travel_and_places"},mosque:{keywords:["islam","worship","minaret"],char:"🕌",fitzpatrick_scale:false,category:"travel_and_places"},synagogue:{keywords:["judaism","worship","temple","jewish"],char:"🕍",fitzpatrick_scale:false,category:"travel_and_places"},kaaba:{keywords:["mecca","mosque","islam"],char:"🕋",fitzpatrick_scale:false,category:"travel_and_places"},shinto_shrine:{keywords:["temple","japan","kyoto"],char:"⛩",fitzpatrick_scale:false,category:"travel_and_places"},watch:{keywords:["time","accessories"],char:"⌚",fitzpatrick_scale:false,category:"objects"},iphone:{keywords:["technology","apple","gadgets","dial"],char:"📱",fitzpatrick_scale:false,category:"objects"},calling:{keywords:["iphone","incoming"],char:"📲",fitzpatrick_scale:false,category:"objects"},computer:{keywords:["technology","laptop","screen","display","monitor"],char:"💻",fitzpatrick_scale:false,category:"objects"},keyboard:{keywords:["technology","computer","type","input","text"],char:"⌨",fitzpatrick_scale:false,category:"objects"},desktop_computer:{keywords:["technology","computing","screen"],char:"🖥",fitzpatrick_scale:false,category:"objects"},printer:{keywords:["paper","ink"],char:"🖨",fitzpatrick_scale:false,category:"objects"},computer_mouse:{keywords:["click"],char:"🖱",fitzpatrick_scale:false,category:"objects"},trackball:{keywords:["technology","trackpad"],char:"🖲",fitzpatrick_scale:false,category:"objects"},joystick:{keywords:["game","play"],char:"🕹",fitzpatrick_scale:false,category:"objects"},clamp:{keywords:["tool"],char:"🗜",fitzpatrick_scale:false,category:"objects"},minidisc:{keywords:["technology","record","data","disk","90s"],char:"💽",fitzpatrick_scale:false,category:"objects"},floppy_disk:{keywords:["oldschool","technology","save","90s","80s"],char:"💾",fitzpatrick_scale:false,category:"objects"},cd:{keywords:["technology","dvd","disk","disc","90s"],char:"💿",fitzpatrick_scale:false,category:"objects"},dvd:{keywords:["cd","disk","disc"],char:"📀",fitzpatrick_scale:false,category:"objects"},vhs:{keywords:["record","video","oldschool","90s","80s"],char:"📼",fitzpatrick_scale:false,category:"objects"},camera:{keywords:["gadgets","photography"],char:"📷",fitzpatrick_scale:false,category:"objects"},camera_flash:{keywords:["photography","gadgets"],char:"📸",fitzpatrick_scale:false,category:"objects"},video_camera:{keywords:["film","record"],char:"📹",fitzpatrick_scale:false,category:"objects"},movie_camera:{keywords:["film","record"],char:"🎥",fitzpatrick_scale:false,category:"objects"},film_projector:{keywords:["video","tape","record","movie"],char:"📽",fitzpatrick_scale:false,category:"objects"},film_strip:{keywords:["movie"],char:"🎞",fitzpatrick_scale:false,category:"objects"},telephone_receiver:{keywords:["technology","communication","dial"],char:"📞",fitzpatrick_scale:false,category:"objects"},phone:{keywords:["technology","communication","dial","telephone"],char:"☎️",fitzpatrick_scale:false,category:"objects"},pager:{keywords:["bbcall","oldschool","90s"],char:"📟",fitzpatrick_scale:false,category:"objects"},fax:{keywords:["communication","technology"],char:"📠",fitzpatrick_scale:false,category:"objects"},tv:{keywords:["technology","program","oldschool","show","television"],char:"📺",fitzpatrick_scale:false,category:"objects"},radio:{keywords:["communication","music","podcast","program"],char:"📻",fitzpatrick_scale:false,category:"objects"},studio_microphone:{keywords:["sing","recording","artist","talkshow"],char:"🎙",fitzpatrick_scale:false,category:"objects"},level_slider:{keywords:["scale"],char:"🎚",fitzpatrick_scale:false,category:"objects"},control_knobs:{keywords:["dial"],char:"🎛",fitzpatrick_scale:false,category:"objects"},compass:{keywords:["magnetic","navigation","orienteering"],char:"🧭",fitzpatrick_scale:false,category:"objects"},stopwatch:{keywords:["time","deadline"],char:"⏱",fitzpatrick_scale:false,category:"objects"},timer_clock:{keywords:["alarm"],char:"⏲",fitzpatrick_scale:false,category:"objects"},alarm_clock:{keywords:["time","wake"],char:"⏰",fitzpatrick_scale:false,category:"objects"},mantelpiece_clock:{keywords:["time"],char:"🕰",fitzpatrick_scale:false,category:"objects"},hourglass_flowing_sand:{keywords:["oldschool","time","countdown"],char:"⏳",fitzpatrick_scale:false,category:"objects"},hourglass:{keywords:["time","clock","oldschool","limit","exam","quiz","test"],char:"⌛",fitzpatrick_scale:false,category:"objects"},satellite:{keywords:["communication","future","radio","space"],char:"📡",fitzpatrick_scale:false,category:"objects"},battery:{keywords:["power","energy","sustain"],char:"🔋",fitzpatrick_scale:false,category:"objects"},electric_plug:{keywords:["charger","power"],char:"🔌",fitzpatrick_scale:false,category:"objects"},bulb:{keywords:["light","electricity","idea"],char:"💡",fitzpatrick_scale:false,category:"objects"},flashlight:{keywords:["dark","camping","sight","night"],char:"🔦",fitzpatrick_scale:false,category:"objects"},candle:{keywords:["fire","wax"],char:"🕯",fitzpatrick_scale:false,category:"objects"},fire_extinguisher:{keywords:["quench"],char:"🧯",fitzpatrick_scale:false,category:"objects"},wastebasket:{keywords:["bin","trash","rubbish","garbage","toss"],char:"🗑",fitzpatrick_scale:false,category:"objects"},oil_drum:{keywords:["barrell"],char:"🛢",fitzpatrick_scale:false,category:"objects"},money_with_wings:{keywords:["dollar","bills","payment","sale"],char:"💸",fitzpatrick_scale:false,category:"objects"},dollar:{keywords:["money","sales","bill","currency"],char:"💵",fitzpatrick_scale:false,category:"objects"},yen:{keywords:["money","sales","japanese","dollar","currency"],char:"💴",fitzpatrick_scale:false,category:"objects"},euro:{keywords:["money","sales","dollar","currency"],char:"💶",fitzpatrick_scale:false,category:"objects"},pound:{keywords:["british","sterling","money","sales","bills","uk","england","currency"],char:"💷",fitzpatrick_scale:false,category:"objects"},moneybag:{keywords:["dollar","payment","coins","sale"],char:"💰",fitzpatrick_scale:false,category:"objects"},credit_card:{keywords:["money","sales","dollar","bill","payment","shopping"],char:"💳",fitzpatrick_scale:false,category:"objects"},gem:{keywords:["blue","ruby","diamond","jewelry"],char:"💎",fitzpatrick_scale:false,category:"objects"},balance_scale:{keywords:["law","fairness","weight"],char:"⚖",fitzpatrick_scale:false,category:"objects"},toolbox:{keywords:["tools","diy","fix","maintainer","mechanic"],char:"🧰",fitzpatrick_scale:false,category:"objects"},wrench:{keywords:["tools","diy","ikea","fix","maintainer"],char:"🔧",fitzpatrick_scale:false,category:"objects"},hammer:{keywords:["tools","build","create"],char:"🔨",fitzpatrick_scale:false,category:"objects"},hammer_and_pick:{keywords:["tools","build","create"],char:"⚒",fitzpatrick_scale:false,category:"objects"},hammer_and_wrench:{keywords:["tools","build","create"],char:"🛠",fitzpatrick_scale:false,category:"objects"},pick:{keywords:["tools","dig"],char:"⛏",fitzpatrick_scale:false,category:"objects"},nut_and_bolt:{keywords:["handy","tools","fix"],char:"🔩",fitzpatrick_scale:false,category:"objects"},gear:{keywords:["cog"],char:"⚙",fitzpatrick_scale:false,category:"objects"},brick:{keywords:["bricks"],char:"🧱",fitzpatrick_scale:false,category:"objects"},chains:{keywords:["lock","arrest"],char:"⛓",fitzpatrick_scale:false,category:"objects"},magnet:{keywords:["attraction","magnetic"],char:"🧲",fitzpatrick_scale:false,category:"objects"},gun:{keywords:["violence","weapon","pistol","revolver"],char:"🔫",fitzpatrick_scale:false,category:"objects"},bomb:{keywords:["boom","explode","explosion","terrorism"],char:"💣",fitzpatrick_scale:false,category:"objects"},firecracker:{keywords:["dynamite","boom","explode","explosion","explosive"],char:"🧨",fitzpatrick_scale:false,category:"objects"},hocho:{keywords:["knife","blade","cutlery","kitchen","weapon"],char:"🔪",fitzpatrick_scale:false,category:"objects"},dagger:{keywords:["weapon"],char:"🗡",fitzpatrick_scale:false,category:"objects"},crossed_swords:{keywords:["weapon"],char:"⚔",fitzpatrick_scale:false,category:"objects"},shield:{keywords:["protection","security"],char:"🛡",fitzpatrick_scale:false,category:"objects"},smoking:{keywords:["kills","tobacco","cigarette","joint","smoke"],char:"🚬",fitzpatrick_scale:false,category:"objects"},skull_and_crossbones:{keywords:["poison","danger","deadly","scary","death","pirate","evil"],char:"☠",fitzpatrick_scale:false,category:"objects"},coffin:{keywords:["vampire","dead","die","death","rip","graveyard","cemetery","casket","funeral","box"],char:"⚰",fitzpatrick_scale:false,category:"objects"},funeral_urn:{keywords:["dead","die","death","rip","ashes"],char:"⚱",fitzpatrick_scale:false,category:"objects"},amphora:{keywords:["vase","jar"],char:"🏺",fitzpatrick_scale:false,category:"objects"},crystal_ball:{keywords:["disco","party","magic","circus","fortune_teller"],char:"🔮",fitzpatrick_scale:false,category:"objects"},prayer_beads:{keywords:["dhikr","religious"],char:"📿",fitzpatrick_scale:false,category:"objects"},nazar_amulet:{keywords:["bead","charm"],char:"🧿",fitzpatrick_scale:false,category:"objects"},barber:{keywords:["hair","salon","style"],char:"💈",fitzpatrick_scale:false,category:"objects"},alembic:{keywords:["distilling","science","experiment","chemistry"],char:"⚗",fitzpatrick_scale:false,category:"objects"},telescope:{keywords:["stars","space","zoom","science","astronomy"],char:"🔭",fitzpatrick_scale:false,category:"objects"},microscope:{keywords:["laboratory","experiment","zoomin","science","study"],char:"🔬",fitzpatrick_scale:false,category:"objects"},hole:{keywords:["embarrassing"],char:"🕳",fitzpatrick_scale:false,category:"objects"},pill:{keywords:["health","medicine","doctor","pharmacy","drug"],char:"💊",fitzpatrick_scale:false,category:"objects"},syringe:{keywords:["health","hospital","drugs","blood","medicine","needle","doctor","nurse"],char:"💉",fitzpatrick_scale:false,category:"objects"},dna:{keywords:["biologist","genetics","life"],char:"🧬",fitzpatrick_scale:false,category:"objects"},microbe:{keywords:["amoeba","bacteria","germs"],char:"🦠",fitzpatrick_scale:false,category:"objects"},petri_dish:{keywords:["bacteria","biology","culture","lab"],char:"🧫",fitzpatrick_scale:false,category:"objects"},test_tube:{keywords:["chemistry","experiment","lab","science"],char:"🧪",fitzpatrick_scale:false,category:"objects"},thermometer:{keywords:["weather","temperature","hot","cold"],char:"🌡",fitzpatrick_scale:false,category:"objects"},broom:{keywords:["cleaning","sweeping","witch"],char:"🧹",fitzpatrick_scale:false,category:"objects"},basket:{keywords:["laundry"],char:"🧺",fitzpatrick_scale:false,category:"objects"},toilet_paper:{keywords:["roll"],char:"🧻",fitzpatrick_scale:false,category:"objects"},label:{keywords:["sale","tag"],char:"🏷",fitzpatrick_scale:false,category:"objects"},bookmark:{keywords:["favorite","label","save"],char:"🔖",fitzpatrick_scale:false,category:"objects"},toilet:{keywords:["restroom","wc","washroom","bathroom","potty"],char:"🚽",fitzpatrick_scale:false,category:"objects"},shower:{keywords:["clean","water","bathroom"],char:"🚿",fitzpatrick_scale:false,category:"objects"},bathtub:{keywords:["clean","shower","bathroom"],char:"🛁",fitzpatrick_scale:false,category:"objects"},soap:{keywords:["bar","bathing","cleaning","lather"],char:"🧼",fitzpatrick_scale:false,category:"objects"},sponge:{keywords:["absorbing","cleaning","porous"],char:"🧽",fitzpatrick_scale:false,category:"objects"},lotion_bottle:{keywords:["moisturizer","sunscreen"],char:"🧴",fitzpatrick_scale:false,category:"objects"},key:{keywords:["lock","door","password"],char:"🔑",fitzpatrick_scale:false,category:"objects"},old_key:{keywords:["lock","door","password"],char:"🗝",fitzpatrick_scale:false,category:"objects"},couch_and_lamp:{keywords:["read","chill"],char:"🛋",fitzpatrick_scale:false,category:"objects"},sleeping_bed:{keywords:["bed","rest"],char:"🛌",fitzpatrick_scale:true,category:"objects"},bed:{keywords:["sleep","rest"],char:"🛏",fitzpatrick_scale:false,category:"objects"},door:{keywords:["house","entry","exit"],char:"🚪",fitzpatrick_scale:false,category:"objects"},bellhop_bell:{keywords:["service"],char:"🛎",fitzpatrick_scale:false,category:"objects"},teddy_bear:{keywords:["plush","stuffed"],char:"🧸",fitzpatrick_scale:false,category:"objects"},framed_picture:{keywords:["photography"],char:"🖼",fitzpatrick_scale:false,category:"objects"},world_map:{keywords:["location","direction"],char:"🗺",fitzpatrick_scale:false,category:"objects"},parasol_on_ground:{keywords:["weather","summer"],char:"⛱",fitzpatrick_scale:false,category:"objects"},moyai:{keywords:["rock","easter island","moai"],char:"🗿",fitzpatrick_scale:false,category:"objects"},shopping:{keywords:["mall","buy","purchase"],char:"🛍",fitzpatrick_scale:false,category:"objects"},shopping_cart:{keywords:["trolley"],char:"🛒",fitzpatrick_scale:false,category:"objects"},balloon:{keywords:["party","celebration","birthday","circus"],char:"🎈",fitzpatrick_scale:false,category:"objects"},flags:{keywords:["fish","japanese","koinobori","carp","banner"],char:"🎏",fitzpatrick_scale:false,category:"objects"},ribbon:{keywords:["decoration","pink","girl","bowtie"],char:"🎀",fitzpatrick_scale:false,category:"objects"},gift:{keywords:["present","birthday","christmas","xmas"],char:"🎁",fitzpatrick_scale:false,category:"objects"},confetti_ball:{keywords:["festival","party","birthday","circus"],char:"🎊",fitzpatrick_scale:false,category:"objects"},tada:{keywords:["party","congratulations","birthday","magic","circus","celebration"],char:"🎉",fitzpatrick_scale:false,category:"objects"},dolls:{keywords:["japanese","toy","kimono"],char:"🎎",fitzpatrick_scale:false,category:"objects"},wind_chime:{keywords:["nature","ding","spring","bell"],char:"🎐",fitzpatrick_scale:false,category:"objects"},crossed_flags:{keywords:["japanese","nation","country","border"],char:"🎌",fitzpatrick_scale:false,category:"objects"},izakaya_lantern:{keywords:["light","paper","halloween","spooky"],char:"🏮",fitzpatrick_scale:false,category:"objects"},red_envelope:{keywords:["gift"],char:"🧧",fitzpatrick_scale:false,category:"objects"},email:{keywords:["letter","postal","inbox","communication"],char:"✉️",fitzpatrick_scale:false,category:"objects"},envelope_with_arrow:{keywords:["email","communication"],char:"📩",fitzpatrick_scale:false,category:"objects"},incoming_envelope:{keywords:["email","inbox"],char:"📨",fitzpatrick_scale:false,category:"objects"},"e-mail":{keywords:["communication","inbox"],char:"📧",fitzpatrick_scale:false,category:"objects"},love_letter:{keywords:["email","like","affection","envelope","valentines"],char:"💌",fitzpatrick_scale:false,category:"objects"},postbox:{keywords:["email","letter","envelope"],char:"📮",fitzpatrick_scale:false,category:"objects"},mailbox_closed:{keywords:["email","communication","inbox"],char:"📪",fitzpatrick_scale:false,category:"objects"},mailbox:{keywords:["email","inbox","communication"],char:"📫",fitzpatrick_scale:false,category:"objects"},mailbox_with_mail:{keywords:["email","inbox","communication"],char:"📬",fitzpatrick_scale:false,category:"objects"},mailbox_with_no_mail:{keywords:["email","inbox"],char:"📭",fitzpatrick_scale:false,category:"objects"},package:{keywords:["mail","gift","cardboard","box","moving"],char:"📦",fitzpatrick_scale:false,category:"objects"},postal_horn:{keywords:["instrument","music"],char:"📯",fitzpatrick_scale:false,category:"objects"},inbox_tray:{keywords:["email","documents"],char:"📥",fitzpatrick_scale:false,category:"objects"},outbox_tray:{keywords:["inbox","email"],char:"📤",fitzpatrick_scale:false,category:"objects"},scroll:{keywords:["documents","ancient","history","paper"],char:"📜",fitzpatrick_scale:false,category:"objects"},page_with_curl:{keywords:["documents","office","paper"],char:"📃",fitzpatrick_scale:false,category:"objects"},bookmark_tabs:{keywords:["favorite","save","order","tidy"],char:"📑",fitzpatrick_scale:false,category:"objects"},receipt:{keywords:["accounting","expenses"],char:"🧾",fitzpatrick_scale:false,category:"objects"},bar_chart:{keywords:["graph","presentation","stats"],char:"📊",fitzpatrick_scale:false,category:"objects"},chart_with_upwards_trend:{keywords:["graph","presentation","stats","recovery","business","economics","money","sales","good","success"],char:"📈",fitzpatrick_scale:false,category:"objects"},chart_with_downwards_trend:{keywords:["graph","presentation","stats","recession","business","economics","money","sales","bad","failure"],char:"📉",fitzpatrick_scale:false,category:"objects"},page_facing_up:{keywords:["documents","office","paper","information"],char:"📄",fitzpatrick_scale:false,category:"objects"},date:{keywords:["calendar","schedule"],char:"📅",fitzpatrick_scale:false,category:"objects"},calendar:{keywords:["schedule","date","planning"],char:"📆",fitzpatrick_scale:false,category:"objects"},spiral_calendar:{keywords:["date","schedule","planning"],char:"🗓",fitzpatrick_scale:false,category:"objects"},card_index:{keywords:["business","stationery"],char:"📇",fitzpatrick_scale:false,category:"objects"},card_file_box:{keywords:["business","stationery"],char:"🗃",fitzpatrick_scale:false,category:"objects"},ballot_box:{keywords:["election","vote"],char:"🗳",fitzpatrick_scale:false,category:"objects"},file_cabinet:{keywords:["filing","organizing"],char:"🗄",fitzpatrick_scale:false,category:"objects"},clipboard:{keywords:["stationery","documents"],char:"📋",fitzpatrick_scale:false,category:"objects"},spiral_notepad:{keywords:["memo","stationery"],char:"🗒",fitzpatrick_scale:false,category:"objects"},file_folder:{keywords:["documents","business","office"],char:"📁",fitzpatrick_scale:false,category:"objects"},open_file_folder:{keywords:["documents","load"],char:"📂",fitzpatrick_scale:false,category:"objects"},card_index_dividers:{keywords:["organizing","business","stationery"],char:"🗂",fitzpatrick_scale:false,category:"objects"},newspaper_roll:{keywords:["press","headline"],char:"🗞",fitzpatrick_scale:false,category:"objects"},newspaper:{keywords:["press","headline"],char:"📰",fitzpatrick_scale:false,category:"objects"},notebook:{keywords:["stationery","record","notes","paper","study"],char:"📓",fitzpatrick_scale:false,category:"objects"},closed_book:{keywords:["read","library","knowledge","textbook","learn"],char:"📕",fitzpatrick_scale:false,category:"objects"},green_book:{keywords:["read","library","knowledge","study"],char:"📗",fitzpatrick_scale:false,category:"objects"},blue_book:{keywords:["read","library","knowledge","learn","study"],char:"📘",fitzpatrick_scale:false,category:"objects"},orange_book:{keywords:["read","library","knowledge","textbook","study"],char:"📙",fitzpatrick_scale:false,category:"objects"},notebook_with_decorative_cover:{keywords:["classroom","notes","record","paper","study"],char:"📔",fitzpatrick_scale:false,category:"objects"},ledger:{keywords:["notes","paper"],char:"📒",fitzpatrick_scale:false,category:"objects"},books:{keywords:["literature","library","study"],char:"📚",fitzpatrick_scale:false,category:"objects"},open_book:{keywords:["book","read","library","knowledge","literature","learn","study"],char:"📖",fitzpatrick_scale:false,category:"objects"},safety_pin:{keywords:["diaper"],char:"🧷",fitzpatrick_scale:false,category:"objects"},link:{keywords:["rings","url"],char:"🔗",fitzpatrick_scale:false,category:"objects"},paperclip:{keywords:["documents","stationery"],char:"📎",fitzpatrick_scale:false,category:"objects"},paperclips:{keywords:["documents","stationery"],char:"🖇",fitzpatrick_scale:false,category:"objects"},scissors:{keywords:["stationery","cut"],char:"✂️",fitzpatrick_scale:false,category:"objects"},triangular_ruler:{keywords:["stationery","math","architect","sketch"],char:"📐",fitzpatrick_scale:false,category:"objects"},straight_ruler:{keywords:["stationery","calculate","length","math","school","drawing","architect","sketch"],char:"📏",fitzpatrick_scale:false,category:"objects"},abacus:{keywords:["calculation"],char:"🧮",fitzpatrick_scale:false,category:"objects"},pushpin:{keywords:["stationery","mark","here"],char:"📌",fitzpatrick_scale:false,category:"objects"},round_pushpin:{keywords:["stationery","location","map","here"],char:"📍",fitzpatrick_scale:false,category:"objects"},triangular_flag_on_post:{keywords:["mark","milestone","place"],char:"🚩",fitzpatrick_scale:false,category:"objects"},white_flag:{keywords:["losing","loser","lost","surrender","give up","fail"],char:"🏳",fitzpatrick_scale:false,category:"objects"},black_flag:{keywords:["pirate"],char:"🏴",fitzpatrick_scale:false,category:"objects"},rainbow_flag:{keywords:["flag","rainbow","pride","gay","lgbt","glbt","queer","homosexual","lesbian","bisexual","transgender"],char:"🏳️‍🌈",fitzpatrick_scale:false,category:"objects"},closed_lock_with_key:{keywords:["security","privacy"],char:"🔐",fitzpatrick_scale:false,category:"objects"},lock:{keywords:["security","password","padlock"],char:"🔒",fitzpatrick_scale:false,category:"objects"},unlock:{keywords:["privacy","security"],char:"🔓",fitzpatrick_scale:false,category:"objects"},lock_with_ink_pen:{keywords:["security","secret"],char:"🔏",fitzpatrick_scale:false,category:"objects"},pen:{keywords:["stationery","writing","write"],char:"🖊",fitzpatrick_scale:false,category:"objects"},fountain_pen:{keywords:["stationery","writing","write"],char:"🖋",fitzpatrick_scale:false,category:"objects"},black_nib:{keywords:["pen","stationery","writing","write"],char:"✒️",fitzpatrick_scale:false,category:"objects"},memo:{keywords:["write","documents","stationery","pencil","paper","writing","legal","exam","quiz","test","study","compose"],char:"📝",fitzpatrick_scale:false,category:"objects"},pencil2:{keywords:["stationery","write","paper","writing","school","study"],char:"✏️",fitzpatrick_scale:false,category:"objects"},crayon:{keywords:["drawing","creativity"],char:"🖍",fitzpatrick_scale:false,category:"objects"},paintbrush:{keywords:["drawing","creativity","art"],char:"🖌",fitzpatrick_scale:false,category:"objects"},mag:{keywords:["search","zoom","find","detective"],char:"🔍",fitzpatrick_scale:false,category:"objects"},mag_right:{keywords:["search","zoom","find","detective"],char:"🔎",fitzpatrick_scale:false,category:"objects"},heart:{keywords:["love","like","valentines"],char:"❤️",fitzpatrick_scale:false,category:"symbols"},orange_heart:{keywords:["love","like","affection","valentines"],char:"🧡",fitzpatrick_scale:false,category:"symbols"},yellow_heart:{keywords:["love","like","affection","valentines"],char:"💛",fitzpatrick_scale:false,category:"symbols"},green_heart:{keywords:["love","like","affection","valentines"],char:"💚",fitzpatrick_scale:false,category:"symbols"},blue_heart:{keywords:["love","like","affection","valentines"],char:"💙",fitzpatrick_scale:false,category:"symbols"},purple_heart:{keywords:["love","like","affection","valentines"],char:"💜",fitzpatrick_scale:false,category:"symbols"},black_heart:{keywords:["evil"],char:"🖤",fitzpatrick_scale:false,category:"symbols"},broken_heart:{keywords:["sad","sorry","break","heart","heartbreak"],char:"💔",fitzpatrick_scale:false,category:"symbols"},heavy_heart_exclamation:{keywords:["decoration","love"],char:"❣",fitzpatrick_scale:false,category:"symbols"},two_hearts:{keywords:["love","like","affection","valentines","heart"],char:"💕",fitzpatrick_scale:false,category:"symbols"},revolving_hearts:{keywords:["love","like","affection","valentines"],char:"💞",fitzpatrick_scale:false,category:"symbols"},heartbeat:{keywords:["love","like","affection","valentines","pink","heart"],char:"💓",fitzpatrick_scale:false,category:"symbols"},heartpulse:{keywords:["like","love","affection","valentines","pink"],char:"💗",fitzpatrick_scale:false,category:"symbols"},sparkling_heart:{keywords:["love","like","affection","valentines"],char:"💖",fitzpatrick_scale:false,category:"symbols"},cupid:{keywords:["love","like","heart","affection","valentines"],char:"💘",fitzpatrick_scale:false,category:"symbols"},gift_heart:{keywords:["love","valentines"],char:"💝",fitzpatrick_scale:false,category:"symbols"},heart_decoration:{keywords:["purple-square","love","like"],char:"💟",fitzpatrick_scale:false,category:"symbols"},peace_symbol:{keywords:["hippie"],char:"☮",fitzpatrick_scale:false,category:"symbols"},latin_cross:{keywords:["christianity"],char:"✝",fitzpatrick_scale:false,category:"symbols"},star_and_crescent:{keywords:["islam"],char:"☪",fitzpatrick_scale:false,category:"symbols"},om:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"🕉",fitzpatrick_scale:false,category:"symbols"},wheel_of_dharma:{keywords:["hinduism","buddhism","sikhism","jainism"],char:"☸",fitzpatrick_scale:false,category:"symbols"},star_of_david:{keywords:["judaism"],char:"✡",fitzpatrick_scale:false,category:"symbols"},six_pointed_star:{keywords:["purple-square","religion","jewish","hexagram"],char:"🔯",fitzpatrick_scale:false,category:"symbols"},menorah:{keywords:["hanukkah","candles","jewish"],char:"🕎",fitzpatrick_scale:false,category:"symbols"},yin_yang:{keywords:["balance"],char:"☯",fitzpatrick_scale:false,category:"symbols"},orthodox_cross:{keywords:["suppedaneum","religion"],char:"☦",fitzpatrick_scale:false,category:"symbols"},place_of_worship:{keywords:["religion","church","temple","prayer"],char:"🛐",fitzpatrick_scale:false,category:"symbols"},ophiuchus:{keywords:["sign","purple-square","constellation","astrology"],char:"⛎",fitzpatrick_scale:false,category:"symbols"},aries:{keywords:["sign","purple-square","zodiac","astrology"],char:"♈",fitzpatrick_scale:false,category:"symbols"},taurus:{keywords:["purple-square","sign","zodiac","astrology"],char:"♉",fitzpatrick_scale:false,category:"symbols"},gemini:{keywords:["sign","zodiac","purple-square","astrology"],char:"♊",fitzpatrick_scale:false,category:"symbols"},cancer:{keywords:["sign","zodiac","purple-square","astrology"],char:"♋",fitzpatrick_scale:false,category:"symbols"},leo:{keywords:["sign","purple-square","zodiac","astrology"],char:"♌",fitzpatrick_scale:false,category:"symbols"},virgo:{keywords:["sign","zodiac","purple-square","astrology"],char:"♍",fitzpatrick_scale:false,category:"symbols"},libra:{keywords:["sign","purple-square","zodiac","astrology"],char:"♎",fitzpatrick_scale:false,category:"symbols"},scorpius:{keywords:["sign","zodiac","purple-square","astrology","scorpio"],char:"♏",fitzpatrick_scale:false,category:"symbols"},sagittarius:{keywords:["sign","zodiac","purple-square","astrology"],char:"♐",fitzpatrick_scale:false,category:"symbols"},capricorn:{keywords:["sign","zodiac","purple-square","astrology"],char:"♑",fitzpatrick_scale:false,category:"symbols"},aquarius:{keywords:["sign","purple-square","zodiac","astrology"],char:"♒",fitzpatrick_scale:false,category:"symbols"},pisces:{keywords:["purple-square","sign","zodiac","astrology"],char:"♓",fitzpatrick_scale:false,category:"symbols"},id:{keywords:["purple-square","words"],char:"🆔",fitzpatrick_scale:false,category:"symbols"},atom_symbol:{keywords:["science","physics","chemistry"],char:"⚛",fitzpatrick_scale:false,category:"symbols"},u7a7a:{keywords:["kanji","japanese","chinese","empty","sky","blue-square"],char:"🈳",fitzpatrick_scale:false,category:"symbols"},u5272:{keywords:["cut","divide","chinese","kanji","pink-square"],char:"🈹",fitzpatrick_scale:false,category:"symbols"},radioactive:{keywords:["nuclear","danger"],char:"☢",fitzpatrick_scale:false,category:"symbols"},biohazard:{keywords:["danger"],char:"☣",fitzpatrick_scale:false,category:"symbols"},mobile_phone_off:{keywords:["mute","orange-square","silence","quiet"],char:"📴",fitzpatrick_scale:false,category:"symbols"},vibration_mode:{keywords:["orange-square","phone"],char:"📳",fitzpatrick_scale:false,category:"symbols"},u6709:{keywords:["orange-square","chinese","have","kanji"],char:"🈶",fitzpatrick_scale:false,category:"symbols"},u7121:{keywords:["nothing","chinese","kanji","japanese","orange-square"],char:"🈚",fitzpatrick_scale:false,category:"symbols"},u7533:{keywords:["chinese","japanese","kanji","orange-square"],char:"🈸",fitzpatrick_scale:false,category:"symbols"},u55b6:{keywords:["japanese","opening hours","orange-square"],char:"🈺",fitzpatrick_scale:false,category:"symbols"},u6708:{keywords:["chinese","month","moon","japanese","orange-square","kanji"],char:"🈷️",fitzpatrick_scale:false,category:"symbols"},eight_pointed_black_star:{keywords:["orange-square","shape","polygon"],char:"✴️",fitzpatrick_scale:false,category:"symbols"},vs:{keywords:["words","orange-square"],char:"🆚",fitzpatrick_scale:false,category:"symbols"},accept:{keywords:["ok","good","chinese","kanji","agree","yes","orange-circle"],char:"🉑",fitzpatrick_scale:false,category:"symbols"},white_flower:{keywords:["japanese","spring"],char:"💮",fitzpatrick_scale:false,category:"symbols"},ideograph_advantage:{keywords:["chinese","kanji","obtain","get","circle"],char:"🉐",fitzpatrick_scale:false,category:"symbols"},secret:{keywords:["privacy","chinese","sshh","kanji","red-circle"],char:"㊙️",fitzpatrick_scale:false,category:"symbols"},congratulations:{keywords:["chinese","kanji","japanese","red-circle"],char:"㊗️",fitzpatrick_scale:false,category:"symbols"},u5408:{keywords:["japanese","chinese","join","kanji","red-square"],char:"🈴",fitzpatrick_scale:false,category:"symbols"},u6e80:{keywords:["full","chinese","japanese","red-square","kanji"],char:"🈵",fitzpatrick_scale:false,category:"symbols"},u7981:{keywords:["kanji","japanese","chinese","forbidden","limit","restricted","red-square"],char:"🈲",fitzpatrick_scale:false,category:"symbols"},a:{keywords:["red-square","alphabet","letter"],char:"🅰️",fitzpatrick_scale:false,category:"symbols"},b:{keywords:["red-square","alphabet","letter"],char:"🅱️",fitzpatrick_scale:false,category:"symbols"},ab:{keywords:["red-square","alphabet"],char:"🆎",fitzpatrick_scale:false,category:"symbols"},cl:{keywords:["alphabet","words","red-square"],char:"🆑",fitzpatrick_scale:false,category:"symbols"},o2:{keywords:["alphabet","red-square","letter"],char:"🅾️",fitzpatrick_scale:false,category:"symbols"},sos:{keywords:["help","red-square","words","emergency","911"],char:"🆘",fitzpatrick_scale:false,category:"symbols"},no_entry:{keywords:["limit","security","privacy","bad","denied","stop","circle"],char:"⛔",fitzpatrick_scale:false,category:"symbols"},name_badge:{keywords:["fire","forbid"],char:"📛",fitzpatrick_scale:false,category:"symbols"},no_entry_sign:{keywords:["forbid","stop","limit","denied","disallow","circle"],char:"🚫",fitzpatrick_scale:false,category:"symbols"},x:{keywords:["no","delete","remove","cancel","red"],char:"❌",fitzpatrick_scale:false,category:"symbols"},o:{keywords:["circle","round"],char:"⭕",fitzpatrick_scale:false,category:"symbols"},stop_sign:{keywords:["stop"],char:"🛑",fitzpatrick_scale:false,category:"symbols"},anger:{keywords:["angry","mad"],char:"💢",fitzpatrick_scale:false,category:"symbols"},hotsprings:{keywords:["bath","warm","relax"],char:"♨️",fitzpatrick_scale:false,category:"symbols"},no_pedestrians:{keywords:["rules","crossing","walking","circle"],char:"🚷",fitzpatrick_scale:false,category:"symbols"},do_not_litter:{keywords:["trash","bin","garbage","circle"],char:"🚯",fitzpatrick_scale:false,category:"symbols"},no_bicycles:{keywords:["cyclist","prohibited","circle"],char:"🚳",fitzpatrick_scale:false,category:"symbols"},"non-potable_water":{keywords:["drink","faucet","tap","circle"],char:"🚱",fitzpatrick_scale:false,category:"symbols"},underage:{keywords:["18","drink","pub","night","minor","circle"],char:"🔞",fitzpatrick_scale:false,category:"symbols"},no_mobile_phones:{keywords:["iphone","mute","circle"],char:"📵",fitzpatrick_scale:false,category:"symbols"},exclamation:{keywords:["heavy_exclamation_mark","danger","surprise","punctuation","wow","warning"],char:"❗",fitzpatrick_scale:false,category:"symbols"},grey_exclamation:{keywords:["surprise","punctuation","gray","wow","warning"],char:"❕",fitzpatrick_scale:false,category:"symbols"},question:{keywords:["doubt","confused"],char:"❓",fitzpatrick_scale:false,category:"symbols"},grey_question:{keywords:["doubts","gray","huh","confused"],char:"❔",fitzpatrick_scale:false,category:"symbols"},bangbang:{keywords:["exclamation","surprise"],char:"‼️",fitzpatrick_scale:false,category:"symbols"},interrobang:{keywords:["wat","punctuation","surprise"],char:"⁉️",fitzpatrick_scale:false,category:"symbols"},100:{keywords:["score","perfect","numbers","century","exam","quiz","test","pass","hundred"],char:"💯",fitzpatrick_scale:false,category:"symbols"},low_brightness:{keywords:["sun","afternoon","warm","summer"],char:"🔅",fitzpatrick_scale:false,category:"symbols"},high_brightness:{keywords:["sun","light"],char:"🔆",fitzpatrick_scale:false,category:"symbols"},trident:{keywords:["weapon","spear"],char:"🔱",fitzpatrick_scale:false,category:"symbols"},fleur_de_lis:{keywords:["decorative","scout"],char:"⚜",fitzpatrick_scale:false,category:"symbols"},part_alternation_mark:{keywords:["graph","presentation","stats","business","economics","bad"],char:"〽️",fitzpatrick_scale:false,category:"symbols"},warning:{keywords:["exclamation","wip","alert","error","problem","issue"],char:"⚠️",fitzpatrick_scale:false,category:"symbols"},children_crossing:{keywords:["school","warning","danger","sign","driving","yellow-diamond"],char:"🚸",fitzpatrick_scale:false,category:"symbols"},beginner:{keywords:["badge","shield"],char:"🔰",fitzpatrick_scale:false,category:"symbols"},recycle:{keywords:["arrow","environment","garbage","trash"],char:"♻️",fitzpatrick_scale:false,category:"symbols"},u6307:{keywords:["chinese","point","green-square","kanji"],char:"🈯",fitzpatrick_scale:false,category:"symbols"},chart:{keywords:["green-square","graph","presentation","stats"],char:"💹",fitzpatrick_scale:false,category:"symbols"},sparkle:{keywords:["stars","green-square","awesome","good","fireworks"],char:"❇️",fitzpatrick_scale:false,category:"symbols"},eight_spoked_asterisk:{keywords:["star","sparkle","green-square"],char:"✳️",fitzpatrick_scale:false,category:"symbols"},negative_squared_cross_mark:{keywords:["x","green-square","no","deny"],char:"❎",fitzpatrick_scale:false,category:"symbols"},white_check_mark:{keywords:["green-square","ok","agree","vote","election","answer","tick"],char:"✅",fitzpatrick_scale:false,category:"symbols"},diamond_shape_with_a_dot_inside:{keywords:["jewel","blue","gem","crystal","fancy"],char:"💠",fitzpatrick_scale:false,category:"symbols"},cyclone:{keywords:["weather","swirl","blue","cloud","vortex","spiral","whirlpool","spin","tornado","hurricane","typhoon"],char:"🌀",fitzpatrick_scale:false,category:"symbols"},loop:{keywords:["tape","cassette"],char:"➿",fitzpatrick_scale:false,category:"symbols"},globe_with_meridians:{keywords:["earth","international","world","internet","interweb","i18n"],char:"🌐",fitzpatrick_scale:false,category:"symbols"},m:{keywords:["alphabet","blue-circle","letter"],char:"Ⓜ️",fitzpatrick_scale:false,category:"symbols"},atm:{keywords:["money","sales","cash","blue-square","payment","bank"],char:"🏧",fitzpatrick_scale:false,category:"symbols"},sa:{keywords:["japanese","blue-square","katakana"],char:"🈂️",fitzpatrick_scale:false,category:"symbols"},passport_control:{keywords:["custom","blue-square"],char:"🛂",fitzpatrick_scale:false,category:"symbols"},customs:{keywords:["passport","border","blue-square"],char:"🛃",fitzpatrick_scale:false,category:"symbols"},baggage_claim:{keywords:["blue-square","airport","transport"],char:"🛄",fitzpatrick_scale:false,category:"symbols"},left_luggage:{keywords:["blue-square","travel"],char:"🛅",fitzpatrick_scale:false,category:"symbols"},wheelchair:{keywords:["blue-square","disabled","a11y","accessibility"],char:"♿",fitzpatrick_scale:false,category:"symbols"},no_smoking:{keywords:["cigarette","blue-square","smell","smoke"],char:"🚭",fitzpatrick_scale:false,category:"symbols"},wc:{keywords:["toilet","restroom","blue-square"],char:"🚾",fitzpatrick_scale:false,category:"symbols"},parking:{keywords:["cars","blue-square","alphabet","letter"],char:"🅿️",fitzpatrick_scale:false,category:"symbols"},potable_water:{keywords:["blue-square","liquid","restroom","cleaning","faucet"],char:"🚰",fitzpatrick_scale:false,category:"symbols"},mens:{keywords:["toilet","restroom","wc","blue-square","gender","male"],char:"🚹",fitzpatrick_scale:false,category:"symbols"},womens:{keywords:["purple-square","woman","female","toilet","loo","restroom","gender"],char:"🚺",fitzpatrick_scale:false,category:"symbols"},baby_symbol:{keywords:["orange-square","child"],char:"🚼",fitzpatrick_scale:false,category:"symbols"},restroom:{keywords:["blue-square","toilet","refresh","wc","gender"],char:"🚻",fitzpatrick_scale:false,category:"symbols"},put_litter_in_its_place:{keywords:["blue-square","sign","human","info"],char:"🚮",fitzpatrick_scale:false,category:"symbols"},cinema:{keywords:["blue-square","record","film","movie","curtain","stage","theater"],char:"🎦",fitzpatrick_scale:false,category:"symbols"},signal_strength:{keywords:["blue-square","reception","phone","internet","connection","wifi","bluetooth","bars"],char:"📶",fitzpatrick_scale:false,category:"symbols"},koko:{keywords:["blue-square","here","katakana","japanese","destination"],char:"🈁",fitzpatrick_scale:false,category:"symbols"},ng:{keywords:["blue-square","words","shape","icon"],char:"🆖",fitzpatrick_scale:false,category:"symbols"},ok:{keywords:["good","agree","yes","blue-square"],char:"🆗",fitzpatrick_scale:false,category:"symbols"},up:{keywords:["blue-square","above","high"],char:"🆙",fitzpatrick_scale:false,category:"symbols"},cool:{keywords:["words","blue-square"],char:"🆒",fitzpatrick_scale:false,category:"symbols"},new:{keywords:["blue-square","words","start"],char:"🆕",fitzpatrick_scale:false,category:"symbols"},free:{keywords:["blue-square","words"],char:"🆓",fitzpatrick_scale:false,category:"symbols"},zero:{keywords:["0","numbers","blue-square","null"],char:"0️⃣",fitzpatrick_scale:false,category:"symbols"},one:{keywords:["blue-square","numbers","1"],char:"1️⃣",fitzpatrick_scale:false,category:"symbols"},two:{keywords:["numbers","2","prime","blue-square"],char:"2️⃣",fitzpatrick_scale:false,category:"symbols"},three:{keywords:["3","numbers","prime","blue-square"],char:"3️⃣",fitzpatrick_scale:false,category:"symbols"},four:{keywords:["4","numbers","blue-square"],char:"4️⃣",fitzpatrick_scale:false,category:"symbols"},five:{keywords:["5","numbers","blue-square","prime"],char:"5️⃣",fitzpatrick_scale:false,category:"symbols"},six:{keywords:["6","numbers","blue-square"],char:"6️⃣",fitzpatrick_scale:false,category:"symbols"},seven:{keywords:["7","numbers","blue-square","prime"],char:"7️⃣",fitzpatrick_scale:false,category:"symbols"},eight:{keywords:["8","blue-square","numbers"],char:"8️⃣",fitzpatrick_scale:false,category:"symbols"},nine:{keywords:["blue-square","numbers","9"],char:"9️⃣",fitzpatrick_scale:false,category:"symbols"},keycap_ten:{keywords:["numbers","10","blue-square"],char:"🔟",fitzpatrick_scale:false,category:"symbols"},asterisk:{keywords:["star","keycap"],char:"*⃣",fitzpatrick_scale:false,category:"symbols"},1234:{keywords:["numbers","blue-square"],char:"🔢",fitzpatrick_scale:false,category:"symbols"},eject_button:{keywords:["blue-square"],char:"⏏️",fitzpatrick_scale:false,category:"symbols"},arrow_forward:{keywords:["blue-square","right","direction","play"],char:"▶️",fitzpatrick_scale:false,category:"symbols"},pause_button:{keywords:["pause","blue-square"],char:"⏸",fitzpatrick_scale:false,category:"symbols"},next_track_button:{keywords:["forward","next","blue-square"],char:"⏭",fitzpatrick_scale:false,category:"symbols"},stop_button:{keywords:["blue-square"],char:"⏹",fitzpatrick_scale:false,category:"symbols"},record_button:{keywords:["blue-square"],char:"⏺",fitzpatrick_scale:false,category:"symbols"},play_or_pause_button:{keywords:["blue-square","play","pause"],char:"⏯",fitzpatrick_scale:false,category:"symbols"},previous_track_button:{keywords:["backward"],char:"⏮",fitzpatrick_scale:false,category:"symbols"},fast_forward:{keywords:["blue-square","play","speed","continue"],char:"⏩",fitzpatrick_scale:false,category:"symbols"},rewind:{keywords:["play","blue-square"],char:"⏪",fitzpatrick_scale:false,category:"symbols"},twisted_rightwards_arrows:{keywords:["blue-square","shuffle","music","random"],char:"🔀",fitzpatrick_scale:false,category:"symbols"},repeat:{keywords:["loop","record"],char:"🔁",fitzpatrick_scale:false,category:"symbols"},repeat_one:{keywords:["blue-square","loop"],char:"🔂",fitzpatrick_scale:false,category:"symbols"},arrow_backward:{keywords:["blue-square","left","direction"],char:"◀️",fitzpatrick_scale:false,category:"symbols"},arrow_up_small:{keywords:["blue-square","triangle","direction","point","forward","top"],char:"🔼",fitzpatrick_scale:false,category:"symbols"},arrow_down_small:{keywords:["blue-square","direction","bottom"],char:"🔽",fitzpatrick_scale:false,category:"symbols"},arrow_double_up:{keywords:["blue-square","direction","top"],char:"⏫",fitzpatrick_scale:false,category:"symbols"},arrow_double_down:{keywords:["blue-square","direction","bottom"],char:"⏬",fitzpatrick_scale:false,category:"symbols"},arrow_right:{keywords:["blue-square","next"],char:"➡️",fitzpatrick_scale:false,category:"symbols"},arrow_left:{keywords:["blue-square","previous","back"],char:"⬅️",fitzpatrick_scale:false,category:"symbols"},arrow_up:{keywords:["blue-square","continue","top","direction"],char:"⬆️",fitzpatrick_scale:false,category:"symbols"},arrow_down:{keywords:["blue-square","direction","bottom"],char:"⬇️",fitzpatrick_scale:false,category:"symbols"},arrow_upper_right:{keywords:["blue-square","point","direction","diagonal","northeast"],char:"↗️",fitzpatrick_scale:false,category:"symbols"},arrow_lower_right:{keywords:["blue-square","direction","diagonal","southeast"],char:"↘️",fitzpatrick_scale:false,category:"symbols"},arrow_lower_left:{keywords:["blue-square","direction","diagonal","southwest"],char:"↙️",fitzpatrick_scale:false,category:"symbols"},arrow_upper_left:{keywords:["blue-square","point","direction","diagonal","northwest"],char:"↖️",fitzpatrick_scale:false,category:"symbols"},arrow_up_down:{keywords:["blue-square","direction","way","vertical"],char:"↕️",fitzpatrick_scale:false,category:"symbols"},left_right_arrow:{keywords:["shape","direction","horizontal","sideways"],char:"↔️",fitzpatrick_scale:false,category:"symbols"},arrows_counterclockwise:{keywords:["blue-square","sync","cycle"],char:"🔄",fitzpatrick_scale:false,category:"symbols"},arrow_right_hook:{keywords:["blue-square","return","rotate","direction"],char:"↪️",fitzpatrick_scale:false,category:"symbols"},leftwards_arrow_with_hook:{keywords:["back","return","blue-square","undo","enter"],char:"↩️",fitzpatrick_scale:false,category:"symbols"},arrow_heading_up:{keywords:["blue-square","direction","top"],char:"⤴️",fitzpatrick_scale:false,category:"symbols"},arrow_heading_down:{keywords:["blue-square","direction","bottom"],char:"⤵️",fitzpatrick_scale:false,category:"symbols"},hash:{keywords:["symbol","blue-square","twitter"],char:"#️⃣",fitzpatrick_scale:false,category:"symbols"},information_source:{keywords:["blue-square","alphabet","letter"],char:"ℹ️",fitzpatrick_scale:false,category:"symbols"},abc:{keywords:["blue-square","alphabet"],char:"🔤",fitzpatrick_scale:false,category:"symbols"},abcd:{keywords:["blue-square","alphabet"],char:"🔡",fitzpatrick_scale:false,category:"symbols"},capital_abcd:{keywords:["alphabet","words","blue-square"],char:"🔠",fitzpatrick_scale:false,category:"symbols"},symbols:{keywords:["blue-square","music","note","ampersand","percent","glyphs","characters"],char:"🔣",fitzpatrick_scale:false,category:"symbols"},musical_note:{keywords:["score","tone","sound"],char:"🎵",fitzpatrick_scale:false,category:"symbols"},notes:{keywords:["music","score"],char:"🎶",fitzpatrick_scale:false,category:"symbols"},wavy_dash:{keywords:["draw","line","moustache","mustache","squiggle","scribble"],char:"〰️",fitzpatrick_scale:false,category:"symbols"},curly_loop:{keywords:["scribble","draw","shape","squiggle"],char:"➰",fitzpatrick_scale:false,category:"symbols"},heavy_check_mark:{keywords:["ok","nike","answer","yes","tick"],char:"✔️",fitzpatrick_scale:false,category:"symbols"},arrows_clockwise:{keywords:["sync","cycle","round","repeat"],char:"🔃",fitzpatrick_scale:false,category:"symbols"},heavy_plus_sign:{keywords:["math","calculation","addition","more","increase"],char:"➕",fitzpatrick_scale:false,category:"symbols"},heavy_minus_sign:{keywords:["math","calculation","subtract","less"],char:"➖",fitzpatrick_scale:false,category:"symbols"},heavy_division_sign:{keywords:["divide","math","calculation"],char:"➗",fitzpatrick_scale:false,category:"symbols"},heavy_multiplication_x:{keywords:["math","calculation"],char:"✖️",fitzpatrick_scale:false,category:"symbols"},infinity:{keywords:["forever"],char:"♾",fitzpatrick_scale:false,category:"symbols"},heavy_dollar_sign:{keywords:["money","sales","payment","currency","buck"],char:"💲",fitzpatrick_scale:false,category:"symbols"},currency_exchange:{keywords:["money","sales","dollar","travel"],char:"💱",fitzpatrick_scale:false,category:"symbols"},copyright:{keywords:["ip","license","circle","law","legal"],char:"©️",fitzpatrick_scale:false,category:"symbols"},registered:{keywords:["alphabet","circle"],char:"®️",fitzpatrick_scale:false,category:"symbols"},tm:{keywords:["trademark","brand","law","legal"],char:"™️",fitzpatrick_scale:false,category:"symbols"},end:{keywords:["words","arrow"],char:"🔚",fitzpatrick_scale:false,category:"symbols"},back:{keywords:["arrow","words","return"],char:"🔙",fitzpatrick_scale:false,category:"symbols"},on:{keywords:["arrow","words"],char:"🔛",fitzpatrick_scale:false,category:"symbols"},top:{keywords:["words","blue-square"],char:"🔝",fitzpatrick_scale:false,category:"symbols"},soon:{keywords:["arrow","words"],char:"🔜",fitzpatrick_scale:false,category:"symbols"},ballot_box_with_check:{keywords:["ok","agree","confirm","black-square","vote","election","yes","tick"],char:"☑️",fitzpatrick_scale:false,category:"symbols"},radio_button:{keywords:["input","old","music","circle"],char:"🔘",fitzpatrick_scale:false,category:"symbols"},white_circle:{keywords:["shape","round"],char:"⚪",fitzpatrick_scale:false,category:"symbols"},black_circle:{keywords:["shape","button","round"],char:"⚫",fitzpatrick_scale:false,category:"symbols"},red_circle:{keywords:["shape","error","danger"],char:"🔴",fitzpatrick_scale:false,category:"symbols"},large_blue_circle:{keywords:["shape","icon","button"],char:"🔵",fitzpatrick_scale:false,category:"symbols"},small_orange_diamond:{keywords:["shape","jewel","gem"],char:"🔸",fitzpatrick_scale:false,category:"symbols"},small_blue_diamond:{keywords:["shape","jewel","gem"],char:"🔹",fitzpatrick_scale:false,category:"symbols"},large_orange_diamond:{keywords:["shape","jewel","gem"],char:"🔶",fitzpatrick_scale:false,category:"symbols"},large_blue_diamond:{keywords:["shape","jewel","gem"],char:"🔷",fitzpatrick_scale:false,category:"symbols"},small_red_triangle:{keywords:["shape","direction","up","top"],char:"🔺",fitzpatrick_scale:false,category:"symbols"},black_small_square:{keywords:["shape","icon"],char:"▪️",fitzpatrick_scale:false,category:"symbols"},white_small_square:{keywords:["shape","icon"],char:"▫️",fitzpatrick_scale:false,category:"symbols"},black_large_square:{keywords:["shape","icon","button"],char:"⬛",fitzpatrick_scale:false,category:"symbols"},white_large_square:{keywords:["shape","icon","stone","button"],char:"⬜",fitzpatrick_scale:false,category:"symbols"},small_red_triangle_down:{keywords:["shape","direction","bottom"],char:"🔻",fitzpatrick_scale:false,category:"symbols"},black_medium_square:{keywords:["shape","button","icon"],char:"◼️",fitzpatrick_scale:false,category:"symbols"},white_medium_square:{keywords:["shape","stone","icon"],char:"◻️",fitzpatrick_scale:false,category:"symbols"},black_medium_small_square:{keywords:["icon","shape","button"],char:"◾",fitzpatrick_scale:false,category:"symbols"},white_medium_small_square:{keywords:["shape","stone","icon","button"],char:"◽",fitzpatrick_scale:false,category:"symbols"},black_square_button:{keywords:["shape","input","frame"],char:"🔲",fitzpatrick_scale:false,category:"symbols"},white_square_button:{keywords:["shape","input"],char:"🔳",fitzpatrick_scale:false,category:"symbols"},speaker:{keywords:["sound","volume","silence","broadcast"],char:"🔈",fitzpatrick_scale:false,category:"symbols"},sound:{keywords:["volume","speaker","broadcast"],char:"🔉",fitzpatrick_scale:false,category:"symbols"},loud_sound:{keywords:["volume","noise","noisy","speaker","broadcast"],char:"🔊",fitzpatrick_scale:false,category:"symbols"},mute:{keywords:["sound","volume","silence","quiet"],char:"🔇",fitzpatrick_scale:false,category:"symbols"},mega:{keywords:["sound","speaker","volume"],char:"📣",fitzpatrick_scale:false,category:"symbols"},loudspeaker:{keywords:["volume","sound"],char:"📢",fitzpatrick_scale:false,category:"symbols"},bell:{keywords:["sound","notification","christmas","xmas","chime"],char:"🔔",fitzpatrick_scale:false,category:"symbols"},no_bell:{keywords:["sound","volume","mute","quiet","silent"],char:"🔕",fitzpatrick_scale:false,category:"symbols"},black_joker:{keywords:["poker","cards","game","play","magic"],char:"🃏",fitzpatrick_scale:false,category:"symbols"},mahjong:{keywords:["game","play","chinese","kanji"],char:"🀄",fitzpatrick_scale:false,category:"symbols"},spades:{keywords:["poker","cards","suits","magic"],char:"♠️",fitzpatrick_scale:false,category:"symbols"},clubs:{keywords:["poker","cards","magic","suits"],char:"♣️",fitzpatrick_scale:false,category:"symbols"},hearts:{keywords:["poker","cards","magic","suits"],char:"♥️",fitzpatrick_scale:false,category:"symbols"},diamonds:{keywords:["poker","cards","magic","suits"],char:"♦️",fitzpatrick_scale:false,category:"symbols"},flower_playing_cards:{keywords:["game","sunset","red"],char:"🎴",fitzpatrick_scale:false,category:"symbols"},thought_balloon:{keywords:["bubble","cloud","speech","thinking","dream"],char:"💭",fitzpatrick_scale:false,category:"symbols"},right_anger_bubble:{keywords:["caption","speech","thinking","mad"],char:"🗯",fitzpatrick_scale:false,category:"symbols"},speech_balloon:{keywords:["bubble","words","message","talk","chatting"],char:"💬",fitzpatrick_scale:false,category:"symbols"},left_speech_bubble:{keywords:["words","message","talk","chatting"],char:"🗨",fitzpatrick_scale:false,category:"symbols"},clock1:{keywords:["time","late","early","schedule"],char:"🕐",fitzpatrick_scale:false,category:"symbols"},clock2:{keywords:["time","late","early","schedule"],char:"🕑",fitzpatrick_scale:false,category:"symbols"},clock3:{keywords:["time","late","early","schedule"],char:"🕒",fitzpatrick_scale:false,category:"symbols"},clock4:{keywords:["time","late","early","schedule"],char:"🕓",fitzpatrick_scale:false,category:"symbols"},clock5:{keywords:["time","late","early","schedule"],char:"🕔",fitzpatrick_scale:false,category:"symbols"},clock6:{keywords:["time","late","early","schedule","dawn","dusk"],char:"🕕",fitzpatrick_scale:false,category:"symbols"},clock7:{keywords:["time","late","early","schedule"],char:"🕖",fitzpatrick_scale:false,category:"symbols"},clock8:{keywords:["time","late","early","schedule"],char:"🕗",fitzpatrick_scale:false,category:"symbols"},clock9:{keywords:["time","late","early","schedule"],char:"🕘",fitzpatrick_scale:false,category:"symbols"},clock10:{keywords:["time","late","early","schedule"],char:"🕙",fitzpatrick_scale:false,category:"symbols"},clock11:{keywords:["time","late","early","schedule"],char:"🕚",fitzpatrick_scale:false,category:"symbols"},clock12:{keywords:["time","noon","midnight","midday","late","early","schedule"],char:"🕛",fitzpatrick_scale:false,category:"symbols"},clock130:{keywords:["time","late","early","schedule"],char:"🕜",fitzpatrick_scale:false,category:"symbols"},clock230:{keywords:["time","late","early","schedule"],char:"🕝",fitzpatrick_scale:false,category:"symbols"},clock330:{keywords:["time","late","early","schedule"],char:"🕞",fitzpatrick_scale:false,category:"symbols"},clock430:{keywords:["time","late","early","schedule"],char:"🕟",fitzpatrick_scale:false,category:"symbols"},clock530:{keywords:["time","late","early","schedule"],char:"🕠",fitzpatrick_scale:false,category:"symbols"},clock630:{keywords:["time","late","early","schedule"],char:"🕡",fitzpatrick_scale:false,category:"symbols"},clock730:{keywords:["time","late","early","schedule"],char:"🕢",fitzpatrick_scale:false,category:"symbols"},clock830:{keywords:["time","late","early","schedule"],char:"🕣",fitzpatrick_scale:false,category:"symbols"},clock930:{keywords:["time","late","early","schedule"],char:"🕤",fitzpatrick_scale:false,category:"symbols"},clock1030:{keywords:["time","late","early","schedule"],char:"🕥",fitzpatrick_scale:false,category:"symbols"},clock1130:{keywords:["time","late","early","schedule"],char:"🕦",fitzpatrick_scale:false,category:"symbols"},clock1230:{keywords:["time","late","early","schedule"],char:"🕧",fitzpatrick_scale:false,category:"symbols"},afghanistan:{keywords:["af","flag","nation","country","banner"],char:"🇦🇫",fitzpatrick_scale:false,category:"flags"},aland_islands:{keywords:["Åland","islands","flag","nation","country","banner"],char:"🇦🇽",fitzpatrick_scale:false,category:"flags"},albania:{keywords:["al","flag","nation","country","banner"],char:"🇦🇱",fitzpatrick_scale:false,category:"flags"},algeria:{keywords:["dz","flag","nation","country","banner"],char:"🇩🇿",fitzpatrick_scale:false,category:"flags"},american_samoa:{keywords:["american","ws","flag","nation","country","banner"],char:"🇦🇸",fitzpatrick_scale:false,category:"flags"},andorra:{keywords:["ad","flag","nation","country","banner"],char:"🇦🇩",fitzpatrick_scale:false,category:"flags"},angola:{keywords:["ao","flag","nation","country","banner"],char:"🇦🇴",fitzpatrick_scale:false,category:"flags"},anguilla:{keywords:["ai","flag","nation","country","banner"],char:"🇦🇮",fitzpatrick_scale:false,category:"flags"},antarctica:{keywords:["aq","flag","nation","country","banner"],char:"🇦🇶",fitzpatrick_scale:false,category:"flags"},antigua_barbuda:{keywords:["antigua","barbuda","flag","nation","country","banner"],char:"🇦🇬",fitzpatrick_scale:false,category:"flags"},argentina:{keywords:["ar","flag","nation","country","banner"],char:"🇦🇷",fitzpatrick_scale:false,category:"flags"},armenia:{keywords:["am","flag","nation","country","banner"],char:"🇦🇲",fitzpatrick_scale:false,category:"flags"},aruba:{keywords:["aw","flag","nation","country","banner"],char:"🇦🇼",fitzpatrick_scale:false,category:"flags"},australia:{keywords:["au","flag","nation","country","banner"],char:"🇦🇺",fitzpatrick_scale:false,category:"flags"},austria:{keywords:["at","flag","nation","country","banner"],char:"🇦🇹",fitzpatrick_scale:false,category:"flags"},azerbaijan:{keywords:["az","flag","nation","country","banner"],char:"🇦🇿",fitzpatrick_scale:false,category:"flags"},bahamas:{keywords:["bs","flag","nation","country","banner"],char:"🇧🇸",fitzpatrick_scale:false,category:"flags"},bahrain:{keywords:["bh","flag","nation","country","banner"],char:"🇧🇭",fitzpatrick_scale:false,category:"flags"},bangladesh:{keywords:["bd","flag","nation","country","banner"],char:"🇧🇩",fitzpatrick_scale:false,category:"flags"},barbados:{keywords:["bb","flag","nation","country","banner"],char:"🇧🇧",fitzpatrick_scale:false,category:"flags"},belarus:{keywords:["by","flag","nation","country","banner"],char:"🇧🇾",fitzpatrick_scale:false,category:"flags"},belgium:{keywords:["be","flag","nation","country","banner"],char:"🇧🇪",fitzpatrick_scale:false,category:"flags"},belize:{keywords:["bz","flag","nation","country","banner"],char:"🇧🇿",fitzpatrick_scale:false,category:"flags"},benin:{keywords:["bj","flag","nation","country","banner"],char:"🇧🇯",fitzpatrick_scale:false,category:"flags"},bermuda:{keywords:["bm","flag","nation","country","banner"],char:"🇧🇲",fitzpatrick_scale:false,category:"flags"},bhutan:{keywords:["bt","flag","nation","country","banner"],char:"🇧🇹",fitzpatrick_scale:false,category:"flags"},bolivia:{keywords:["bo","flag","nation","country","banner"],char:"🇧🇴",fitzpatrick_scale:false,category:"flags"},caribbean_netherlands:{keywords:["bonaire","flag","nation","country","banner"],char:"🇧🇶",fitzpatrick_scale:false,category:"flags"},bosnia_herzegovina:{keywords:["bosnia","herzegovina","flag","nation","country","banner"],char:"🇧🇦",fitzpatrick_scale:false,category:"flags"},botswana:{keywords:["bw","flag","nation","country","banner"],char:"🇧🇼",fitzpatrick_scale:false,category:"flags"},brazil:{keywords:["br","flag","nation","country","banner"],char:"🇧🇷",fitzpatrick_scale:false,category:"flags"},british_indian_ocean_territory:{keywords:["british","indian","ocean","territory","flag","nation","country","banner"],char:"🇮🇴",fitzpatrick_scale:false,category:"flags"},british_virgin_islands:{keywords:["british","virgin","islands","bvi","flag","nation","country","banner"],char:"🇻🇬",fitzpatrick_scale:false,category:"flags"},brunei:{keywords:["bn","darussalam","flag","nation","country","banner"],char:"🇧🇳",fitzpatrick_scale:false,category:"flags"},bulgaria:{keywords:["bg","flag","nation","country","banner"],char:"🇧🇬",fitzpatrick_scale:false,category:"flags"},burkina_faso:{keywords:["burkina","faso","flag","nation","country","banner"],char:"🇧🇫",fitzpatrick_scale:false,category:"flags"},burundi:{keywords:["bi","flag","nation","country","banner"],char:"🇧🇮",fitzpatrick_scale:false,category:"flags"},cape_verde:{keywords:["cabo","verde","flag","nation","country","banner"],char:"🇨🇻",fitzpatrick_scale:false,category:"flags"},cambodia:{keywords:["kh","flag","nation","country","banner"],char:"🇰🇭",fitzpatrick_scale:false,category:"flags"},cameroon:{keywords:["cm","flag","nation","country","banner"],char:"🇨🇲",fitzpatrick_scale:false,category:"flags"},canada:{keywords:["ca","flag","nation","country","banner"],char:"🇨🇦",fitzpatrick_scale:false,category:"flags"},canary_islands:{keywords:["canary","islands","flag","nation","country","banner"],char:"🇮🇨",fitzpatrick_scale:false,category:"flags"},cayman_islands:{keywords:["cayman","islands","flag","nation","country","banner"],char:"🇰🇾",fitzpatrick_scale:false,category:"flags"},central_african_republic:{keywords:["central","african","republic","flag","nation","country","banner"],char:"🇨🇫",fitzpatrick_scale:false,category:"flags"},chad:{keywords:["td","flag","nation","country","banner"],char:"🇹🇩",fitzpatrick_scale:false,category:"flags"},chile:{keywords:["flag","nation","country","banner"],char:"🇨🇱",fitzpatrick_scale:false,category:"flags"},cn:{keywords:["china","chinese","prc","flag","country","nation","banner"],char:"🇨🇳",fitzpatrick_scale:false,category:"flags"},christmas_island:{keywords:["christmas","island","flag","nation","country","banner"],char:"🇨🇽",fitzpatrick_scale:false,category:"flags"},cocos_islands:{keywords:["cocos","keeling","islands","flag","nation","country","banner"],char:"🇨🇨",fitzpatrick_scale:false,category:"flags"},colombia:{keywords:["co","flag","nation","country","banner"],char:"🇨🇴",fitzpatrick_scale:false,category:"flags"},comoros:{keywords:["km","flag","nation","country","banner"],char:"🇰🇲",fitzpatrick_scale:false,category:"flags"},congo_brazzaville:{keywords:["congo","flag","nation","country","banner"],char:"🇨🇬",fitzpatrick_scale:false,category:"flags"},congo_kinshasa:{keywords:["congo","democratic","republic","flag","nation","country","banner"],char:"🇨🇩",fitzpatrick_scale:false,category:"flags"},cook_islands:{keywords:["cook","islands","flag","nation","country","banner"],char:"🇨🇰",fitzpatrick_scale:false,category:"flags"},costa_rica:{keywords:["costa","rica","flag","nation","country","banner"],char:"🇨🇷",fitzpatrick_scale:false,category:"flags"},croatia:{keywords:["hr","flag","nation","country","banner"],char:"🇭🇷",fitzpatrick_scale:false,category:"flags"},cuba:{keywords:["cu","flag","nation","country","banner"],char:"🇨🇺",fitzpatrick_scale:false,category:"flags"},curacao:{keywords:["curaçao","flag","nation","country","banner"],char:"🇨🇼",fitzpatrick_scale:false,category:"flags"},cyprus:{keywords:["cy","flag","nation","country","banner"],char:"🇨🇾",fitzpatrick_scale:false,category:"flags"},czech_republic:{keywords:["cz","flag","nation","country","banner"],char:"🇨🇿",fitzpatrick_scale:false,category:"flags"},denmark:{keywords:["dk","flag","nation","country","banner"],char:"🇩🇰",fitzpatrick_scale:false,category:"flags"},djibouti:{keywords:["dj","flag","nation","country","banner"],char:"🇩🇯",fitzpatrick_scale:false,category:"flags"},dominica:{keywords:["dm","flag","nation","country","banner"],char:"🇩🇲",fitzpatrick_scale:false,category:"flags"},dominican_republic:{keywords:["dominican","republic","flag","nation","country","banner"],char:"🇩🇴",fitzpatrick_scale:false,category:"flags"},ecuador:{keywords:["ec","flag","nation","country","banner"],char:"🇪🇨",fitzpatrick_scale:false,category:"flags"},egypt:{keywords:["eg","flag","nation","country","banner"],char:"🇪🇬",fitzpatrick_scale:false,category:"flags"},el_salvador:{keywords:["el","salvador","flag","nation","country","banner"],char:"🇸🇻",fitzpatrick_scale:false,category:"flags"},equatorial_guinea:{keywords:["equatorial","gn","flag","nation","country","banner"],char:"🇬🇶",fitzpatrick_scale:false,category:"flags"},eritrea:{keywords:["er","flag","nation","country","banner"],char:"🇪🇷",fitzpatrick_scale:false,category:"flags"},estonia:{keywords:["ee","flag","nation","country","banner"],char:"🇪🇪",fitzpatrick_scale:false,category:"flags"},ethiopia:{keywords:["et","flag","nation","country","banner"],char:"🇪🇹",fitzpatrick_scale:false,category:"flags"},eu:{keywords:["european","union","flag","banner"],char:"🇪🇺",fitzpatrick_scale:false,category:"flags"},falkland_islands:{keywords:["falkland","islands","malvinas","flag","nation","country","banner"],char:"🇫🇰",fitzpatrick_scale:false,category:"flags"},faroe_islands:{keywords:["faroe","islands","flag","nation","country","banner"],char:"🇫🇴",fitzpatrick_scale:false,category:"flags"},fiji:{keywords:["fj","flag","nation","country","banner"],char:"🇫🇯",fitzpatrick_scale:false,category:"flags"},finland:{keywords:["fi","flag","nation","country","banner"],char:"🇫🇮",fitzpatrick_scale:false,category:"flags"},fr:{keywords:["banner","flag","nation","france","french","country"],char:"🇫🇷",fitzpatrick_scale:false,category:"flags"},french_guiana:{keywords:["french","guiana","flag","nation","country","banner"],char:"🇬🇫",fitzpatrick_scale:false,category:"flags"},french_polynesia:{keywords:["french","polynesia","flag","nation","country","banner"],char:"🇵🇫",fitzpatrick_scale:false,category:"flags"},french_southern_territories:{keywords:["french","southern","territories","flag","nation","country","banner"],char:"🇹🇫",fitzpatrick_scale:false,category:"flags"},gabon:{keywords:["ga","flag","nation","country","banner"],char:"🇬🇦",fitzpatrick_scale:false,category:"flags"},gambia:{keywords:["gm","flag","nation","country","banner"],char:"🇬🇲",fitzpatrick_scale:false,category:"flags"},georgia:{keywords:["ge","flag","nation","country","banner"],char:"🇬🇪",fitzpatrick_scale:false,category:"flags"},de:{keywords:["german","nation","flag","country","banner"],char:"🇩🇪",fitzpatrick_scale:false,category:"flags"},ghana:{keywords:["gh","flag","nation","country","banner"],char:"🇬🇭",fitzpatrick_scale:false,category:"flags"},gibraltar:{keywords:["gi","flag","nation","country","banner"],char:"🇬🇮",fitzpatrick_scale:false,category:"flags"},greece:{keywords:["gr","flag","nation","country","banner"],char:"🇬🇷",fitzpatrick_scale:false,category:"flags"},greenland:{keywords:["gl","flag","nation","country","banner"],char:"🇬🇱",fitzpatrick_scale:false,category:"flags"},grenada:{keywords:["gd","flag","nation","country","banner"],char:"🇬🇩",fitzpatrick_scale:false,category:"flags"},guadeloupe:{keywords:["gp","flag","nation","country","banner"],char:"🇬🇵",fitzpatrick_scale:false,category:"flags"},guam:{keywords:["gu","flag","nation","country","banner"],char:"🇬🇺",fitzpatrick_scale:false,category:"flags"},guatemala:{keywords:["gt","flag","nation","country","banner"],char:"🇬🇹",fitzpatrick_scale:false,category:"flags"},guernsey:{keywords:["gg","flag","nation","country","banner"],char:"🇬🇬",fitzpatrick_scale:false,category:"flags"},guinea:{keywords:["gn","flag","nation","country","banner"],char:"🇬🇳",fitzpatrick_scale:false,category:"flags"},guinea_bissau:{keywords:["gw","bissau","flag","nation","country","banner"],char:"🇬🇼",fitzpatrick_scale:false,category:"flags"},guyana:{keywords:["gy","flag","nation","country","banner"],char:"🇬🇾",fitzpatrick_scale:false,category:"flags"},haiti:{keywords:["ht","flag","nation","country","banner"],char:"🇭🇹",fitzpatrick_scale:false,category:"flags"},honduras:{keywords:["hn","flag","nation","country","banner"],char:"🇭🇳",fitzpatrick_scale:false,category:"flags"},hong_kong:{keywords:["hong","kong","flag","nation","country","banner"],char:"🇭🇰",fitzpatrick_scale:false,category:"flags"},hungary:{keywords:["hu","flag","nation","country","banner"],char:"🇭🇺",fitzpatrick_scale:false,category:"flags"},iceland:{keywords:["is","flag","nation","country","banner"],char:"🇮🇸",fitzpatrick_scale:false,category:"flags"},india:{keywords:["in","flag","nation","country","banner"],char:"🇮🇳",fitzpatrick_scale:false,category:"flags"},indonesia:{keywords:["flag","nation","country","banner"],char:"🇮🇩",fitzpatrick_scale:false,category:"flags"},iran:{keywords:["iran,","islamic","republic","flag","nation","country","banner"],char:"🇮🇷",fitzpatrick_scale:false,category:"flags"},iraq:{keywords:["iq","flag","nation","country","banner"],char:"🇮🇶",fitzpatrick_scale:false,category:"flags"},ireland:{keywords:["ie","flag","nation","country","banner"],char:"🇮🇪",fitzpatrick_scale:false,category:"flags"},isle_of_man:{keywords:["isle","man","flag","nation","country","banner"],char:"🇮🇲",fitzpatrick_scale:false,category:"flags"},israel:{keywords:["il","flag","nation","country","banner"],char:"🇮🇱",fitzpatrick_scale:false,category:"flags"},it:{keywords:["italy","flag","nation","country","banner"],char:"🇮🇹",fitzpatrick_scale:false,category:"flags"},cote_divoire:{keywords:["ivory","coast","flag","nation","country","banner"],char:"🇨🇮",fitzpatrick_scale:false,category:"flags"},jamaica:{keywords:["jm","flag","nation","country","banner"],char:"🇯🇲",fitzpatrick_scale:false,category:"flags"},jp:{keywords:["japanese","nation","flag","country","banner"],char:"🇯🇵",fitzpatrick_scale:false,category:"flags"},jersey:{keywords:["je","flag","nation","country","banner"],char:"🇯🇪",fitzpatrick_scale:false,category:"flags"},jordan:{keywords:["jo","flag","nation","country","banner"],char:"🇯🇴",fitzpatrick_scale:false,category:"flags"},kazakhstan:{keywords:["kz","flag","nation","country","banner"],char:"🇰🇿",fitzpatrick_scale:false,category:"flags"},kenya:{keywords:["ke","flag","nation","country","banner"],char:"🇰🇪",fitzpatrick_scale:false,category:"flags"},kiribati:{keywords:["ki","flag","nation","country","banner"],char:"🇰🇮",fitzpatrick_scale:false,category:"flags"},kosovo:{keywords:["xk","flag","nation","country","banner"],char:"🇽🇰",fitzpatrick_scale:false,category:"flags"},kuwait:{keywords:["kw","flag","nation","country","banner"],char:"🇰🇼",fitzpatrick_scale:false,category:"flags"},kyrgyzstan:{keywords:["kg","flag","nation","country","banner"],char:"🇰🇬",fitzpatrick_scale:false,category:"flags"},laos:{keywords:["lao","democratic","republic","flag","nation","country","banner"],char:"🇱🇦",fitzpatrick_scale:false,category:"flags"},latvia:{keywords:["lv","flag","nation","country","banner"],char:"🇱🇻",fitzpatrick_scale:false,category:"flags"},lebanon:{keywords:["lb","flag","nation","country","banner"],char:"🇱🇧",fitzpatrick_scale:false,category:"flags"},lesotho:{keywords:["ls","flag","nation","country","banner"],char:"🇱🇸",fitzpatrick_scale:false,category:"flags"},liberia:{keywords:["lr","flag","nation","country","banner"],char:"🇱🇷",fitzpatrick_scale:false,category:"flags"},libya:{keywords:["ly","flag","nation","country","banner"],char:"🇱🇾",fitzpatrick_scale:false,category:"flags"},liechtenstein:{keywords:["li","flag","nation","country","banner"],char:"🇱🇮",fitzpatrick_scale:false,category:"flags"},lithuania:{keywords:["lt","flag","nation","country","banner"],char:"🇱🇹",fitzpatrick_scale:false,category:"flags"},luxembourg:{keywords:["lu","flag","nation","country","banner"],char:"🇱🇺",fitzpatrick_scale:false,category:"flags"},macau:{keywords:["macao","flag","nation","country","banner"],char:"🇲🇴",fitzpatrick_scale:false,category:"flags"},macedonia:{keywords:["macedonia,","flag","nation","country","banner"],char:"🇲🇰",fitzpatrick_scale:false,category:"flags"},madagascar:{keywords:["mg","flag","nation","country","banner"],char:"🇲🇬",fitzpatrick_scale:false,category:"flags"},malawi:{keywords:["mw","flag","nation","country","banner"],char:"🇲🇼",fitzpatrick_scale:false,category:"flags"},malaysia:{keywords:["my","flag","nation","country","banner"],char:"🇲🇾",fitzpatrick_scale:false,category:"flags"},maldives:{keywords:["mv","flag","nation","country","banner"],char:"🇲🇻",fitzpatrick_scale:false,category:"flags"},mali:{keywords:["ml","flag","nation","country","banner"],char:"🇲🇱",fitzpatrick_scale:false,category:"flags"},malta:{keywords:["mt","flag","nation","country","banner"],char:"🇲🇹",fitzpatrick_scale:false,category:"flags"},marshall_islands:{keywords:["marshall","islands","flag","nation","country","banner"],char:"🇲🇭",fitzpatrick_scale:false,category:"flags"},martinique:{keywords:["mq","flag","nation","country","banner"],char:"🇲🇶",fitzpatrick_scale:false,category:"flags"},mauritania:{keywords:["mr","flag","nation","country","banner"],char:"🇲🇷",fitzpatrick_scale:false,category:"flags"},mauritius:{keywords:["mu","flag","nation","country","banner"],char:"🇲🇺",fitzpatrick_scale:false,category:"flags"},mayotte:{keywords:["yt","flag","nation","country","banner"],char:"🇾🇹",fitzpatrick_scale:false,category:"flags"},mexico:{keywords:["mx","flag","nation","country","banner"],char:"🇲🇽",fitzpatrick_scale:false,category:"flags"},micronesia:{keywords:["micronesia,","federated","states","flag","nation","country","banner"],char:"🇫🇲",fitzpatrick_scale:false,category:"flags"},moldova:{keywords:["moldova,","republic","flag","nation","country","banner"],char:"🇲🇩",fitzpatrick_scale:false,category:"flags"},monaco:{keywords:["mc","flag","nation","country","banner"],char:"🇲🇨",fitzpatrick_scale:false,category:"flags"},mongolia:{keywords:["mn","flag","nation","country","banner"],char:"🇲🇳",fitzpatrick_scale:false,category:"flags"},montenegro:{keywords:["me","flag","nation","country","banner"],char:"🇲🇪",fitzpatrick_scale:false,category:"flags"},montserrat:{keywords:["ms","flag","nation","country","banner"],char:"🇲🇸",fitzpatrick_scale:false,category:"flags"},morocco:{keywords:["ma","flag","nation","country","banner"],char:"🇲🇦",fitzpatrick_scale:false,category:"flags"},mozambique:{keywords:["mz","flag","nation","country","banner"],char:"🇲🇿",fitzpatrick_scale:false,category:"flags"},myanmar:{keywords:["mm","flag","nation","country","banner"],char:"🇲🇲",fitzpatrick_scale:false,category:"flags"},namibia:{keywords:["na","flag","nation","country","banner"],char:"🇳🇦",fitzpatrick_scale:false,category:"flags"},nauru:{keywords:["nr","flag","nation","country","banner"],char:"🇳🇷",fitzpatrick_scale:false,category:"flags"},nepal:{keywords:["np","flag","nation","country","banner"],char:"🇳🇵",fitzpatrick_scale:false,category:"flags"},netherlands:{keywords:["nl","flag","nation","country","banner"],char:"🇳🇱",fitzpatrick_scale:false,category:"flags"},new_caledonia:{keywords:["new","caledonia","flag","nation","country","banner"],char:"🇳🇨",fitzpatrick_scale:false,category:"flags"},new_zealand:{keywords:["new","zealand","flag","nation","country","banner"],char:"🇳🇿",fitzpatrick_scale:false,category:"flags"},nicaragua:{keywords:["ni","flag","nation","country","banner"],char:"🇳🇮",fitzpatrick_scale:false,category:"flags"},niger:{keywords:["ne","flag","nation","country","banner"],char:"🇳🇪",fitzpatrick_scale:false,category:"flags"},nigeria:{keywords:["flag","nation","country","banner"],char:"🇳🇬",fitzpatrick_scale:false,category:"flags"},niue:{keywords:["nu","flag","nation","country","banner"],char:"🇳🇺",fitzpatrick_scale:false,category:"flags"},norfolk_island:{keywords:["norfolk","island","flag","nation","country","banner"],char:"🇳🇫",fitzpatrick_scale:false,category:"flags"},northern_mariana_islands:{keywords:["northern","mariana","islands","flag","nation","country","banner"],char:"🇲🇵",fitzpatrick_scale:false,category:"flags"},north_korea:{keywords:["north","korea","nation","flag","country","banner"],char:"🇰🇵",fitzpatrick_scale:false,category:"flags"},norway:{keywords:["no","flag","nation","country","banner"],char:"🇳🇴",fitzpatrick_scale:false,category:"flags"},oman:{keywords:["om_symbol","flag","nation","country","banner"],char:"🇴🇲",fitzpatrick_scale:false,category:"flags"},pakistan:{keywords:["pk","flag","nation","country","banner"],char:"🇵🇰",fitzpatrick_scale:false,category:"flags"},palau:{keywords:["pw","flag","nation","country","banner"],char:"🇵🇼",fitzpatrick_scale:false,category:"flags"},palestinian_territories:{keywords:["palestine","palestinian","territories","flag","nation","country","banner"],char:"🇵🇸",fitzpatrick_scale:false,category:"flags"},panama:{keywords:["pa","flag","nation","country","banner"],char:"🇵🇦",fitzpatrick_scale:false,category:"flags"},papua_new_guinea:{keywords:["papua","new","guinea","flag","nation","country","banner"],char:"🇵🇬",fitzpatrick_scale:false,category:"flags"},paraguay:{keywords:["py","flag","nation","country","banner"],char:"🇵🇾",fitzpatrick_scale:false,category:"flags"},peru:{keywords:["pe","flag","nation","country","banner"],char:"🇵🇪",fitzpatrick_scale:false,category:"flags"},philippines:{keywords:["ph","flag","nation","country","banner"],char:"🇵🇭",fitzpatrick_scale:false,category:"flags"},pitcairn_islands:{keywords:["pitcairn","flag","nation","country","banner"],char:"🇵🇳",fitzpatrick_scale:false,category:"flags"},poland:{keywords:["pl","flag","nation","country","banner"],char:"🇵🇱",fitzpatrick_scale:false,category:"flags"},portugal:{keywords:["pt","flag","nation","country","banner"],char:"🇵🇹",fitzpatrick_scale:false,category:"flags"},puerto_rico:{keywords:["puerto","rico","flag","nation","country","banner"],char:"🇵🇷",fitzpatrick_scale:false,category:"flags"},qatar:{keywords:["qa","flag","nation","country","banner"],char:"🇶🇦",fitzpatrick_scale:false,category:"flags"},reunion:{keywords:["réunion","flag","nation","country","banner"],char:"🇷🇪",fitzpatrick_scale:false,category:"flags"},romania:{keywords:["ro","flag","nation","country","banner"],char:"🇷🇴",fitzpatrick_scale:false,category:"flags"},ru:{keywords:["russian","federation","flag","nation","country","banner"],char:"🇷🇺",fitzpatrick_scale:false,category:"flags"},rwanda:{keywords:["rw","flag","nation","country","banner"],char:"🇷🇼",fitzpatrick_scale:false,category:"flags"},st_barthelemy:{keywords:["saint","barthélemy","flag","nation","country","banner"],char:"🇧🇱",fitzpatrick_scale:false,category:"flags"},st_helena:{keywords:["saint","helena","ascension","tristan","cunha","flag","nation","country","banner"],char:"🇸🇭",fitzpatrick_scale:false,category:"flags"},st_kitts_nevis:{keywords:["saint","kitts","nevis","flag","nation","country","banner"],char:"🇰🇳",fitzpatrick_scale:false,category:"flags"},st_lucia:{keywords:["saint","lucia","flag","nation","country","banner"],char:"🇱🇨",fitzpatrick_scale:false,category:"flags"},st_pierre_miquelon:{keywords:["saint","pierre","miquelon","flag","nation","country","banner"],char:"🇵🇲",fitzpatrick_scale:false,category:"flags"},st_vincent_grenadines:{keywords:["saint","vincent","grenadines","flag","nation","country","banner"],char:"🇻🇨",fitzpatrick_scale:false,category:"flags"},samoa:{keywords:["ws","flag","nation","country","banner"],char:"🇼🇸",fitzpatrick_scale:false,category:"flags"},san_marino:{keywords:["san","marino","flag","nation","country","banner"],char:"🇸🇲",fitzpatrick_scale:false,category:"flags"},sao_tome_principe:{keywords:["sao","tome","principe","flag","nation","country","banner"],char:"🇸🇹",fitzpatrick_scale:false,category:"flags"},saudi_arabia:{keywords:["flag","nation","country","banner"],char:"🇸🇦",fitzpatrick_scale:false,category:"flags"},senegal:{keywords:["sn","flag","nation","country","banner"],char:"🇸🇳",fitzpatrick_scale:false,category:"flags"},serbia:{keywords:["rs","flag","nation","country","banner"],char:"🇷🇸",fitzpatrick_scale:false,category:"flags"},seychelles:{keywords:["sc","flag","nation","country","banner"],char:"🇸🇨",fitzpatrick_scale:false,category:"flags"},sierra_leone:{keywords:["sierra","leone","flag","nation","country","banner"],char:"🇸🇱",fitzpatrick_scale:false,category:"flags"},singapore:{keywords:["sg","flag","nation","country","banner"],char:"🇸🇬",fitzpatrick_scale:false,category:"flags"},sint_maarten:{keywords:["sint","maarten","dutch","flag","nation","country","banner"],char:"🇸🇽",fitzpatrick_scale:false,category:"flags"},slovakia:{keywords:["sk","flag","nation","country","banner"],char:"🇸🇰",fitzpatrick_scale:false,category:"flags"},slovenia:{keywords:["si","flag","nation","country","banner"],char:"🇸🇮",fitzpatrick_scale:false,category:"flags"},solomon_islands:{keywords:["solomon","islands","flag","nation","country","banner"],char:"🇸🇧",fitzpatrick_scale:false,category:"flags"},somalia:{keywords:["so","flag","nation","country","banner"],char:"🇸🇴",fitzpatrick_scale:false,category:"flags"},south_africa:{keywords:["south","africa","flag","nation","country","banner"],char:"🇿🇦",fitzpatrick_scale:false,category:"flags"},south_georgia_south_sandwich_islands:{keywords:["south","georgia","sandwich","islands","flag","nation","country","banner"],char:"🇬🇸",fitzpatrick_scale:false,category:"flags"},kr:{keywords:["south","korea","nation","flag","country","banner"],char:"🇰🇷",fitzpatrick_scale:false,category:"flags"},south_sudan:{keywords:["south","sd","flag","nation","country","banner"],char:"🇸🇸",fitzpatrick_scale:false,category:"flags"},es:{keywords:["spain","flag","nation","country","banner"],char:"🇪🇸",fitzpatrick_scale:false,category:"flags"},sri_lanka:{keywords:["sri","lanka","flag","nation","country","banner"],char:"🇱🇰",fitzpatrick_scale:false,category:"flags"},sudan:{keywords:["sd","flag","nation","country","banner"],char:"🇸🇩",fitzpatrick_scale:false,category:"flags"},suriname:{keywords:["sr","flag","nation","country","banner"],char:"🇸🇷",fitzpatrick_scale:false,category:"flags"},swaziland:{keywords:["sz","flag","nation","country","banner"],char:"🇸🇿",fitzpatrick_scale:false,category:"flags"},sweden:{keywords:["se","flag","nation","country","banner"],char:"🇸🇪",fitzpatrick_scale:false,category:"flags"},switzerland:{keywords:["ch","flag","nation","country","banner"],char:"🇨🇭",fitzpatrick_scale:false,category:"flags"},syria:{keywords:["syrian","arab","republic","flag","nation","country","banner"],char:"🇸🇾",fitzpatrick_scale:false,category:"flags"},taiwan:{keywords:["tw","flag","nation","country","banner"],char:"🇹🇼",fitzpatrick_scale:false,category:"flags"},tajikistan:{keywords:["tj","flag","nation","country","banner"],char:"🇹🇯",fitzpatrick_scale:false,category:"flags"},tanzania:{keywords:["tanzania,","united","republic","flag","nation","country","banner"],char:"🇹🇿",fitzpatrick_scale:false,category:"flags"},thailand:{keywords:["th","flag","nation","country","banner"],char:"🇹🇭",fitzpatrick_scale:false,category:"flags"},timor_leste:{keywords:["timor","leste","flag","nation","country","banner"],char:"🇹🇱",fitzpatrick_scale:false,category:"flags"},togo:{keywords:["tg","flag","nation","country","banner"],char:"🇹🇬",fitzpatrick_scale:false,category:"flags"},tokelau:{keywords:["tk","flag","nation","country","banner"],char:"🇹🇰",fitzpatrick_scale:false,category:"flags"},tonga:{keywords:["to","flag","nation","country","banner"],char:"🇹🇴",fitzpatrick_scale:false,category:"flags"},trinidad_tobago:{keywords:["trinidad","tobago","flag","nation","country","banner"],char:"🇹🇹",fitzpatrick_scale:false,category:"flags"},tunisia:{keywords:["tn","flag","nation","country","banner"],char:"🇹🇳",fitzpatrick_scale:false,category:"flags"},tr:{keywords:["turkey","flag","nation","country","banner"],char:"🇹🇷",fitzpatrick_scale:false,category:"flags"},turkmenistan:{keywords:["flag","nation","country","banner"],char:"🇹🇲",fitzpatrick_scale:false,category:"flags"},turks_caicos_islands:{keywords:["turks","caicos","islands","flag","nation","country","banner"],char:"🇹🇨",fitzpatrick_scale:false,category:"flags"},tuvalu:{keywords:["flag","nation","country","banner"],char:"🇹🇻",fitzpatrick_scale:false,category:"flags"},uganda:{keywords:["ug","flag","nation","country","banner"],char:"🇺🇬",fitzpatrick_scale:false,category:"flags"},ukraine:{keywords:["ua","flag","nation","country","banner"],char:"🇺🇦",fitzpatrick_scale:false,category:"flags"},united_arab_emirates:{keywords:["united","arab","emirates","flag","nation","country","banner"],char:"🇦🇪",fitzpatrick_scale:false,category:"flags"},uk:{keywords:["united","kingdom","great","britain","northern","ireland","flag","nation","country","banner","british","UK","english","england","union jack"],char:"🇬🇧",fitzpatrick_scale:false,category:"flags"},england:{keywords:["flag","english"],char:"🏴󠁧󠁢󠁥󠁮󠁧󠁿",fitzpatrick_scale:false,category:"flags"},scotland:{keywords:["flag","scottish"],char:"🏴󠁧󠁢󠁳󠁣󠁴󠁿",fitzpatrick_scale:false,category:"flags"},wales:{keywords:["flag","welsh"],char:"🏴󠁧󠁢󠁷󠁬󠁳󠁿",fitzpatrick_scale:false,category:"flags"},us:{keywords:["united","states","america","flag","nation","country","banner"],char:"🇺🇸",fitzpatrick_scale:false,category:"flags"},us_virgin_islands:{keywords:["virgin","islands","us","flag","nation","country","banner"],char:"🇻🇮",fitzpatrick_scale:false,category:"flags"},uruguay:{keywords:["uy","flag","nation","country","banner"],char:"🇺🇾",fitzpatrick_scale:false,category:"flags"},uzbekistan:{keywords:["uz","flag","nation","country","banner"],char:"🇺🇿",fitzpatrick_scale:false,category:"flags"},vanuatu:{keywords:["vu","flag","nation","country","banner"],char:"🇻🇺",fitzpatrick_scale:false,category:"flags"},vatican_city:{keywords:["vatican","city","flag","nation","country","banner"],char:"🇻🇦",fitzpatrick_scale:false,category:"flags"},venezuela:{keywords:["ve","bolivarian","republic","flag","nation","country","banner"],char:"🇻🇪",fitzpatrick_scale:false,category:"flags"},vietnam:{keywords:["viet","nam","flag","nation","country","banner"],char:"🇻🇳",fitzpatrick_scale:false,category:"flags"},wallis_futuna:{keywords:["wallis","futuna","flag","nation","country","banner"],char:"🇼🇫",fitzpatrick_scale:false,category:"flags"},western_sahara:{keywords:["western","sahara","flag","nation","country","banner"],char:"🇪🇭",fitzpatrick_scale:false,category:"flags"},yemen:{keywords:["ye","flag","nation","country","banner"],char:"🇾🇪",fitzpatrick_scale:false,category:"flags"},zambia:{keywords:["zm","flag","nation","country","banner"],char:"🇿🇲",fitzpatrick_scale:false,category:"flags"},zimbabwe:{keywords:["zw","flag","nation","country","banner"],char:"🇿🇼",fitzpatrick_scale:false,category:"flags"},united_nations:{keywords:["un","flag","banner"],char:"🇺🇳",fitzpatrick_scale:false,category:"flags"},pirate_flag:{keywords:["skull","crossbones","flag","banner"],char:"🏴‍☠️",fitzpatrick_scale:false,category:"flags"}}); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ar.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.ar', '

بدء التنقل بواسطة لوحة المفاتيح

\n' + '\n' + '
\n' + '
التركيز على شريط القوائم
\n' + '
نظاما التشغيل Windows أو Linux: Alt + F9
\n' + '
نظام التشغيل macOS: ⌥F9
\n' + '
التركيز على شريط الأدوات
\n' + '
نظاما التشغيل Windows أو Linux: Alt + F10
\n' + '
نظام التشغيل macOS: ⌥F10
\n' + '
التركيز على التذييل
\n' + '
نظاما التشغيل Windows أو Linux: Alt + F11
\n' + '
نظام التشغيل macOS: ⌥F11
\n' + '
تركيز الإشعارات
\n' + '
نظاما التشغيل Windows أو Linux: Alt + F12
\n' + '
نظام التشغيل macOS: ⌥F12
\n' + '
التركيز على شريط أدوات السياق
\n' + '
أنظمة التشغيل Windows أو Linux أو macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

سيبدأ التنقل عند عنصر واجهة المستخدم الأول، والذي سيتم تمييزه أو تسطيره في حالة العنصر الأول في\n' + ' مسار عنصر التذييل.

\n' + '\n' + '

التنقل بين أقسام واجهة المستخدم

\n' + '\n' + '

للانتقال من أحد أقسام واجهة المستخدم إلى القسم التالي، اضغط على Tab.

\n' + '\n' + '

للانتقال من أحد أقسام واجهة المستخدم إلى القسم السابق، اضغط على Shift+Tab.

\n' + '\n' + '

ترتيب علامات Tab لأقسام واجهة المستخدم هذه هو:

\n' + '\n' + '
    \n' + '
  1. شريط القوائم
  2. \n' + '
  3. كل مجموعة شريط الأدوات
  4. \n' + '
  5. الشريط الجانبي
  6. \n' + '
  7. مسار العنصر في التذييل
  8. \n' + '
  9. زر تبديل عدد الكلمات في التذييل
  10. \n' + '
  11. رابط إدراج العلامة التجارية في التذييل
  12. \n' + '
  13. مؤشر تغيير حجم المحرر في التذييل
  14. \n' + '
\n' + '\n' + '

إذا لم يكن قسم واجهة المستخدم موجودًا، فسيتم تخطيه.

\n' + '\n' + '

إذا كان التذييل يحتوي على التركيز على ‏‫التنقل بواسطة لوحة المفاتيح، ولا يوجد شريط جانبي مرئي، فإن الضغط على Shift+Tab\n' + ' ينقل التركيز إلى مجموعة شريط الأدوات الأولى، وليس الأخيرة.

\n' + '\n' + '

التنقل بين أقسام واجهة المستخدم

\n' + '\n' + '

للانتقال من أحد عناصر واجهة المستخدم إلى العنصر التالي، اضغط على مفتاح السهم المناسب.

\n' + '\n' + '

مفتاحا السهمين اليسار‎ واليمين‎

\n' + '\n' + '\n' + '\n' + '

مفتاحا السهمين لأسفل‎ ولأعلى‎

\n' + '\n' + '\n' + '\n' + '

دورة مفاتيح الأسهم‎ داخل قسم واجهة المستخدم التي تم التركيز عليها.

\n' + '\n' + '

لإغلاق قائمة مفتوحة أو قائمة فرعية مفتوحة أو قائمة منبثقة مفتوحة، اضغط على مفتاح Esc.

\n' + '\n' + '

إذا كان التركيز الحالي على "الجزء العلوي" من قسم معين لواجهة المستخدم، فإن الضغط على مفتاح Esc يؤدي أيضًا إلى الخروج\n' + ' من التنقل بواسطة لوحة المفاتيح بالكامل.

\n' + '\n' + '

تنفيذ عنصر قائمة أو زر شريط أدوات

\n' + '\n' + '

عندما يتم تمييز عنصر القائمة المطلوب أو زر شريط الأدوات، اضغط على زر Return، أو Enter،\n' + ' أو مفتاح المسافة لتنفيذ العنصر.

\n' + '\n' + '

التنقل في مربعات الحوار غير المبوبة

\n' + '\n' + '

في مربعات الحوار غير المبوبة، يتم التركيز على المكون التفاعلي الأول عند فتح مربع الحوار.

\n' + '\n' + '

التنقل بين مكونات الحوار التفاعلي بالضغط على زر Tab أو Shift+Tab.

\n' + '\n' + '

التنقل في مربعات الحوار المبوبة

\n' + '\n' + '

في مربعات الحوار المبوبة، يتم التركيز على الزر الأول في قائمة علامات التبويب عند فتح مربع الحوار.

\n' + '\n' + '

التنقل بين المكونات التفاعلية لعلامة التبويب لمربع الحوار هذه بالضغط على زر Tab أو\n' + ' Shift+Tab.

\n' + '\n' + '

التبديل إلى علامة تبويب أخرى لمربع الحوار من خلال التركيز على قائمة علامة التبويب ثم الضغط على زر السهم المناسب\n' + ' مفتاح للتنقل بين علامات التبويب المتاحة.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/bg_BG.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.bg_BG', '

Начало на навигацията с клавиатурата

\n' + '\n' + '
\n' + '
Фокусиране върху лентата с менюта
\n' + '
Windows или Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Фокусиране върху лентата с инструменти
\n' + '
Windows или Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Фокусиране върху долния колонтитул
\n' + '
Windows или Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Фокусиране на известието
\n' + '
Windows или Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Фокусиране върху контекстуалната лента с инструменти
\n' + '
Windows, Linux или macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Навигацията ще започне с първия елемент на ПИ, който ще бъде маркиран или подчертан в случая на първия елемент в\n' + ' пътя до елемента в долния колонтитул.

\n' + '\n' + '

Навигиране между раздели на ПИ

\n' + '\n' + '

За да преминете от един раздел на ПИ към следващия, натиснете Tab.

\n' + '\n' + '

За да преминете от един раздел на ПИ към предишния, натиснете Shift+Tab.

\n' + '\n' + '

Редът за обхождане с табулация на тези раздели на ПИ е:

\n' + '\n' + '
    \n' + '
  1. Лентата с менюта
  2. \n' + '
  3. Всяка група на лентата с инструменти
  4. \n' + '
  5. Страничната лента
  6. \n' + '
  7. Пътят до елемента в долния колонтитул
  8. \n' + '
  9. Бутонът за превключване на броя на думите в долния колонтитул
  10. \n' + '
  11. Връзката за търговска марка в долния колонтитул
  12. \n' + '
  13. Манипулаторът за преоразмеряване на редактора в долния колонтитул
  14. \n' + '
\n' + '\n' + '

Ако някой раздел на ПИ липсва, той се пропуска.

\n' + '\n' + '

Ако долният колонтитул има фокус за навигация с клавиатурата и няма странична лента, натискането на Shift+Tab\n' + ' премества фокуса към първата група на лентата с инструменти, а не към последната.

\n' + '\n' + '

Навигиране в разделите на ПИ

\n' + '\n' + '

За да преминете от един елемент на ПИ към следващия, натиснете съответния клавиш със стрелка.

\n' + '\n' + '

С клавишите със стрелка наляво и надясно

\n' + '\n' + '\n' + '\n' + '

С клавишите със стрелка надолу и нагоре

\n' + '\n' + '\n' + '\n' + '

Клавишите със стрелки се придвижват в рамките на фокусирания раздел на ПИ.

\n' + '\n' + '

За да затворите отворено меню, подменю или изскачащо меню, натиснете клавиша Esc.

\n' + '\n' + '

Ако текущият фокус е върху „горната част“ на конкретен раздел на ПИ, натискането на клавиша Esc също излиза\n' + ' напълно от навигацията с клавиатурата.

\n' + '\n' + '

Изпълнение на елемент от менюто или бутон от лентата с инструменти

\n' + '\n' + '

Когато желаният елемент от менюто или бутон от лентата с инструменти е маркиран, натиснете Return, Enter\n' + ' или клавиша за интервал, за да изпълните елемента.

\n' + '\n' + '

Навигиране в диалогови прозорци без раздели

\n' + '\n' + '

В диалоговите прозорци без раздели първият интерактивен компонент се фокусира, когато се отвори диалоговият прозорец.

\n' + '\n' + '

Навигирайте между интерактивните компоненти на диалоговия прозорец, като натиснете Tab или Shift+Tab.

\n' + '\n' + '

Навигиране в диалогови прозорци с раздели

\n' + '\n' + '

В диалоговите прозорци с раздели първият бутон в менюто с раздели се фокусира, когато се отвори диалоговият прозорец.

\n' + '\n' + '

Навигирайте между интерактивните компоненти на този диалогов раздел, като натиснете Tab или\n' + ' Shift+Tab.

\n' + '\n' + '

Превключете към друг диалогов раздел, като фокусирате върху менюто с раздели и след това натиснете съответния клавиш със стрелка,\n' + ' за да преминете през наличните раздели.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ca.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.ca', '

Inici de la navegació amb el teclat

\n' + '\n' + '
\n' + '
Enfocar la barra de menús
\n' + '
Windows o Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + "
Enfocar la barra d'eines
\n" + '
Windows o Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Enfocar el peu de pàgina
\n' + '
Windows o Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Enfocar la notificació
\n' + '
Windows o Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + "
Enfocar una barra d'eines contextual
\n" + '
Windows, Linux o macOS: Ctrl+F9
\n' + '
\n' + '\n' + "

La navegació començarà en el primer element de la interfície d'usuari, que es ressaltarà o subratllarà per al primer element a\n" + " la ruta de l'element de peu de pàgina.

\n" + '\n' + "

Navegació entre seccions de la interfície d'usuari

\n" + '\n' + "

Per desplaçar-vos des d'una secció de la interfície d'usuari a la següent, premeu la tecla Tab.

\n" + '\n' + "

Per desplaçar-vos des d'una secció de la interfície d'usuari a l'anterior, premeu les tecles Maj+Tab.

\n" + '\n' + "

L'ordre en prémer la tecla Tab d'aquestes secciones de la interfície d'usuari és:

\n" + '\n' + '
    \n' + '
  1. Barra de menús
  2. \n' + "
  3. Cada grup de la barra d'eines
  4. \n" + '
  5. Barra lateral
  6. \n' + "
  7. Ruta de l'element del peu de pàgina
  8. \n" + '
  9. Botó de commutació de recompte de paraules al peu de pàgina
  10. \n' + '
  11. Enllaç de marca del peu de pàgina
  12. \n' + "
  13. Control de canvi de mida de l'editor al peu de pàgina
  14. \n" + '
\n' + '\n' + "

Si no hi ha una secció de la interfície d'usuari, s'ometrà.

\n" + '\n' + '

Si el peu de pàgina té el focus de navegació del teclat i no hi ha cap barra lateral visible, en prémer Maj+Tab\n' + " el focus es mou al primer grup de la barra d'eines, no l'últim.

\n" + '\n' + "

Navegació dins de les seccions de la interfície d'usuari

\n" + '\n' + "

Per desplaçar-vos des d'un element de la interfície d'usuari al següent, premeu la tecla de Fletxa adequada.

\n" + '\n' + '

Les tecles de fletxa Esquerra i Dreta

\n' + '\n' + '\n' + '\n' + '

Les tecles de fletxa Avall i Amunt

\n' + '\n' + '\n' + '\n' + "

Les tecles de Fletxa us permeten desplaçar-vos dins de la secció de la interfície d'usuari que té el focus.

\n" + '\n' + '

Per tancar un menú, un submenú o un menú emergent oberts, premeu la tecla Esc.

\n' + '\n' + "

Si el focus actual es troba a la ‘part superior’ d'una secció específica de la interfície d'usuari, en prémer la tecla Esc també es tanca\n" + ' completament la navegació amb el teclat.

\n' + '\n' + "

Execució d'un element de menú o d'un botó de la barra d'eines

\n" + '\n' + "

Quan l'element del menú o el botó de la barra d'eines que desitgeu estigui ressaltat, premeu Retorn, Intro\n" + " o la barra d'espai per executar l'element.

\n" + '\n' + '

Navegació per quadres de diàleg sense pestanyes

\n' + '\n' + "

En els quadres de diàleg sense pestanyes, el primer component interactiu pren el focus quan s'obre el quadre diàleg.

\n" + '\n' + '

Premeu la tecla Tab o les tecles Maj+Tab per desplaçar-vos entre components interactius del quadre de diàleg.

\n' + '\n' + '

Navegació per quadres de diàleg amb pestanyes

\n' + '\n' + "

En els quadres de diàleg amb pestanyes, el primer botó del menú de la pestanya pren el focus quan s'obre el quadre diàleg.

\n" + '\n' + "

Per desplaçar-vos entre components interactius d'aquest quadre de diàleg, premeu la tecla Tab o\n" + ' les tecles Maj+Tab.

\n' + '\n' + "

Canvieu a la pestanya d'un altre quadre de diàleg, tot enfocant el menú de la pestanya, i després premeu la tecla Fletxa adequada\n" + ' per canviar entre les pestanyes disponibles.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/cs.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.cs', '

Začínáme navigovat pomocí klávesnice

\n' + '\n' + '
\n' + '
Přejít na řádek nabídek
\n' + '
Windows nebo Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Přejít na panel nástrojů
\n' + '
Windows nebo Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Přejít na zápatí
\n' + '
Windows nebo Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Přejít na oznámení
\n' + '
Windows nebo Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Přejít na kontextový panel nástrojů
\n' + '
Windows, Linux nebo macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigace začne u první položky uživatelského rozhraní, která bude zvýrazněna nebo v případě první položky\n' + ' cesty k prvku zápatí podtržena.

\n' + '\n' + '

Navigace mezi oddíly uživatelského rozhraní

\n' + '\n' + '

Stisknutím klávesy Tab se posunete z jednoho oddílu uživatelského rozhraní na další.

\n' + '\n' + '

Stisknutím kláves Shift+Tab se posunete z jednoho oddílu uživatelského rozhraní na předchozí.

\n' + '\n' + '

Pořadí přepínání mezi oddíly uživatelského rozhraní pomocí klávesy Tab:

\n' + '\n' + '
    \n' + '
  1. Řádek nabídek
  2. \n' + '
  3. Každá skupina panelu nástrojů
  4. \n' + '
  5. Boční panel
  6. \n' + '
  7. Cesta k prvku v zápatí.
  8. \n' + '
  9. Tlačítko přepínače počtu slov v zápatí
  10. \n' + '
  11. Odkaz na informace o značce v zápatí
  12. \n' + '
  13. Úchyt pro změnu velikosti editoru v zápatí
  14. \n' + '
\n' + '\n' + '

Pokud nějaký oddíl uživatelského rozhraní není přítomen, je přeskočen.

\n' + '\n' + '

Pokud je zápatí vybrané pro navigaci pomocí klávesnice a není zobrazen žádný boční panel, stisknutím kláves Shift+Tab\n' + ' přejdete na první skupinu panelu nástrojů, nikoli na poslední.

\n' + '\n' + '

Navigace v rámci oddílů uživatelského rozhraní

\n' + '\n' + '

Chcete-li se přesunout z jednoho prvku uživatelského rozhraní na další, stiskněte příslušnou klávesu s šipkou.

\n' + '\n' + '

Klávesy s šipkou vlevovpravo

\n' + '\n' + '\n' + '\n' + '

Klávesy se šipkou dolůnahoru

\n' + '\n' + '\n' + '\n' + '

Šipky provádí přepínání v rámci vybraného oddílu uživatelského rozhraní.

\n' + '\n' + '

Chcete-li zavřít otevřenou nabídku, podnabídku nebo místní nabídku, stiskněte klávesu Esc.

\n' + '\n' + '

Pokud je aktuálně vybrána horní část oddílu uživatelského rozhraní, stisknutím klávesy Esc zcela ukončíte také\n' + ' navigaci pomocí klávesnice.

\n' + '\n' + '

Provedení příkazu položky nabídky nebo tlačítka panelu nástrojů

\n' + '\n' + '

Pokud je zvýrazněna požadovaná položka nabídky nebo tlačítko panelu nástrojů, stisknutím klávesy Return, Enter\n' + ' nebo mezerníku provedete příslušný příkaz.

\n' + '\n' + '

Navigace v dialogových oknech bez záložek

\n' + '\n' + '

Při otevření dialogových oken bez záložek přejdete na první interaktivní komponentu.

\n' + '\n' + '

Přecházet mezi interaktivními komponentami dialogového okna můžete stisknutím klávesy Tab nebo kombinace Shift+Tab.

\n' + '\n' + '

Navigace v dialogových oknech se záložkami

\n' + '\n' + '

Při otevření dialogových oken se záložkami přejdete na první tlačítko v nabídce záložek.

\n' + '\n' + '

Přecházet mezi interaktivními komponentami této záložky dialogového okna můžete stisknutím klávesy Tab nebo\n' + ' kombinace Shift+Tab.

\n' + '\n' + '

Chcete-li přepnout na další záložku dialogového okna, přejděte na nabídku záložek a poté můžete stisknutím požadované šipky\n' + ' přepínat mezi dostupnými záložkami.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/da.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.da', '

Start tastaturnavigation

\n' + '\n' + '
\n' + '
Fokuser på menulinjen
\n' + '
Windows eller Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Fokuser på værktøjslinjen
\n' + '
Windows eller Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Fokuser på sidefoden
\n' + '
Windows eller Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Fokuser på meddelelsen
\n' + '
Windows eller Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Fokuser på kontekstuel værktøjslinje
\n' + '
Windows, Linux eller macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigationen starter ved det første UI-element, som fremhæves eller understreges hvad angår det første element i\n' + ' sidefodens sti til elementet.

\n' + '\n' + '

Naviger mellem UI-sektioner

\n' + '\n' + '

Gå fra én UI-sektion til den næste ved at trykke på Tab.

\n' + '\n' + '

Gå fra én UI-sektion til den forrige ved at trykke på Shift+Tab.

\n' + '\n' + '

Tab-rækkefølgen af disse UI-sektioner er:

\n' + '\n' + '
    \n' + '
  1. Menulinje
  2. \n' + '
  3. Hver værktøjsgruppe
  4. \n' + '
  5. Sidepanel
  6. \n' + '
  7. Sti til elementet i sidefoden
  8. \n' + '
  9. Til/fra-knap for ordoptælling i sidefoden
  10. \n' + '
  11. Brandinglink i sidefoden
  12. \n' + '
  13. Tilpasningshåndtag for editor i sidefoden
  14. \n' + '
\n' + '\n' + '

Hvis en UI-sektion ikke er til stede, springes den over.

\n' + '\n' + '

Hvis sidefoden har fokus til tastaturnavigation, og der ikke er noget synligt sidepanel, kan der trykkes på Shift+Tab\n' + ' for at flytte fokus til den første værktøjsgruppe, ikke den sidste.

\n' + '\n' + '

Naviger inden for UI-sektioner

\n' + '\n' + '

Gå fra ét UI-element til det næste ved at trykke på den relevante piletast.

\n' + '\n' + '

Venstre og højre piletast

\n' + '\n' + '\n' + '\n' + '

Pil ned og op

\n' + '\n' + '\n' + '\n' + '

Piletasterne kører rundt inden for UI-sektionen, der fokuseres på.

\n' + '\n' + '

For at lukke en åben menu, en åben undermenu eller en åben genvejsmenu trykkes der på Esc-tasten.

\n' + '\n' + "

Hvis det aktuelle fokus er i 'toppen' af en bestemt UI-sektion, vil tryk på Esc-tasten også afslutte\n" + ' tastaturnavigationen helt.

\n' + '\n' + '

Udfør et menupunkt eller en værktøjslinjeknap

\n' + '\n' + '

Når det ønskede menupunkt eller den ønskede værktøjslinjeknap er fremhævet, trykkes der på Retur, Enter\n' + ' eller mellemrumstasten for at udføre elementet.

\n' + '\n' + '

Naviger i ikke-faneopdelte dialogbokse

\n' + '\n' + '

I ikke-faneopdelte dialogbokse får den første interaktive komponent fokus, når dialogboksen åbnes.

\n' + '\n' + '

Naviger mellem interaktive dialogbokskomponenter ved at trykke på Tab eller Shift+Tab.

\n' + '\n' + '

Naviger i faneopdelte dialogbokse

\n' + '\n' + '

I faneopdelte dialogbokse får den første knap i fanemenuen fokus, når dialogboksen åbnes.

\n' + '\n' + '

Naviger mellem interaktive komponenter i denne dialogboksfane ved at trykke på Tab eller\n' + ' Shift+Tab.

\n' + '\n' + '

Skift til en anden dialogboksfane ved at fokusere på fanemenuen og derefter trykke på den relevante piletast\n' + ' for at køre igennem de tilgængelige faner.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/de.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.de', '

Grundlagen der Tastaturnavigation

\n' + '\n' + '
\n' + '
Fokus auf Menüleiste
\n' + '
Windows oder Linux: ALT+F9
\n' + '
macOS: ⌥F9
\n' + '
Fokus auf Symbolleiste
\n' + '
Windows oder Linux: ALT+F10
\n' + '
macOS: ⌥F10
\n' + '
Fokus auf Fußzeile
\n' + '
Windows oder Linux: ALT+F11
\n' + '
macOS: ⌥F11
\n' + '
Benachrichtigung fokussieren
\n' + '
Windows oder Linux: ALT+F12
\n' + '
macOS: ⌥F12
\n' + '
Fokus auf kontextbezogene Symbolleiste
\n' + '
Windows, Linux oder macOS: STRG+F9
\n' + '
\n' + '\n' + '

Die Navigation beginnt beim ersten Benutzeroberflächenelement, welches hervorgehoben ist. Falls sich das erste Element im Pfad der Fußzeile befindet,\n' + ' ist es unterstrichen.

\n' + '\n' + '

Zwischen Abschnitten der Benutzeroberfläche navigieren

\n' + '\n' + '

Um von einem Abschnitt der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie TAB.

\n' + '\n' + '

Um von einem Abschnitt der Benutzeroberfläche zum vorherigen zu wechseln, drücken Sie UMSCHALT+TAB.

\n' + '\n' + '

Die Abschnitte der Benutzeroberfläche haben folgende TAB-Reihenfolge:

\n' + '\n' + '
    \n' + '
  1. Menüleiste
  2. \n' + '
  3. Einzelne Gruppen der Symbolleiste
  4. \n' + '
  5. Randleiste
  6. \n' + '
  7. Elementpfad in der Fußzeile
  8. \n' + '
  9. Umschaltfläche „Wörter zählen“ in der Fußzeile
  10. \n' + '
  11. Branding-Link in der Fußzeile
  12. \n' + '
  13. Editor-Ziehpunkt zur Größenänderung in der Fußzeile
  14. \n' + '
\n' + '\n' + '

Falls ein Abschnitt der Benutzeroberflächen nicht vorhanden ist, wird er übersprungen.

\n' + '\n' + '

Wenn in der Fußzeile die Tastaturnavigation fokussiert ist und keine Randleiste angezeigt wird, wechselt der Fokus durch Drücken von UMSCHALT+TAB\n' + ' zur ersten Gruppe der Symbolleiste, nicht zur letzten.

\n' + '\n' + '

Innerhalb von Abschnitten der Benutzeroberfläche navigieren

\n' + '\n' + '

Um von einem Element der Benutzeroberfläche zum nächsten zu wechseln, drücken Sie die entsprechende Pfeiltaste.

\n' + '\n' + '

Die Pfeiltasten Links und Rechts

\n' + '\n' + '\n' + '\n' + '

Die Pfeiltasten Abwärts und Aufwärts

\n' + '\n' + '\n' + '\n' + '

Die Pfeiltasten rotieren innerhalb des fokussierten Abschnitts der Benutzeroberfläche.

\n' + '\n' + '

Um ein geöffnetes Menü, ein geöffnetes Untermenü oder ein geöffnetes Popupmenü zu schließen, drücken Sie die ESC-Taste.

\n' + '\n' + '

Wenn sich der aktuelle Fokus ganz oben in einem bestimmten Abschnitt der Benutzeroberfläche befindet, wird durch Drücken der ESC-Taste auch\n' + ' die Tastaturnavigation beendet.

\n' + '\n' + '

Ein Menüelement oder eine Symbolleistenschaltfläche ausführen

\n' + '\n' + '

Wenn das gewünschte Menüelement oder die gewünschte Symbolleistenschaltfläche hervorgehoben ist, drücken Sie Zurück, Eingabe\n' + ' oder die Leertaste, um das Element auszuführen.

\n' + '\n' + '

In Dialogfeldern ohne Registerkarten navigieren

\n' + '\n' + '

In Dialogfeldern ohne Registerkarten ist beim Öffnen eines Dialogfelds die erste interaktive Komponente fokussiert.

\n' + '\n' + '

Navigieren Sie zwischen den interaktiven Komponenten eines Dialogfelds, indem Sie TAB oder UMSCHALT+TAB drücken.

\n' + '\n' + '

In Dialogfeldern mit Registerkarten navigieren

\n' + '\n' + '

In Dialogfeldern mit Registerkarten ist beim Öffnen eines Dialogfelds die erste Schaltfläche eines Registerkartenmenüs fokussiert.

\n' + '\n' + '

Navigieren Sie zwischen den interaktiven Komponenten auf dieser Registerkarte des Dialogfelds, indem Sie TAB oder\n' + ' UMSCHALT+TAB drücken.

\n' + '\n' + '

Wechseln Sie zu einer anderen Registerkarte des Dialogfelds, indem Sie den Fokus auf das Registerkartenmenü legen und dann die entsprechende Pfeiltaste\n' + ' drücken, um durch die verfügbaren Registerkarten zu rotieren.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/el.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.el', '

Έναρξη πλοήγησης μέσω πληκτρολογίου

\n' + '\n' + '
\n' + '
Εστίαση στη γραμμή μενού
\n' + '
Windows ή Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Εστίαση στη γραμμή εργαλείων
\n' + '
Windows ή Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Εστίαση στο υποσέλιδο
\n' + '
Windows ή Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Εστίαση στην ειδοποίηση
\n' + '
Windows ή Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Εστίαση σε γραμμή εργαλείων βάσει περιεχομένου
\n' + '
Windows, Linux ή macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Η πλοήγηση θα ξεκινήσει από το πρώτο στοιχείο περιβάλλοντος χρήστη, που θα επισημαίνεται ή θα είναι υπογραμμισμένο,\n' + ' όπως στην περίπτωση της διαδρομής του στοιχείου Υποσέλιδου.

\n' + '\n' + '

Πλοήγηση μεταξύ ενοτήτων του περιβάλλοντος χρήστη

\n' + '\n' + '

Για να μετακινηθείτε από μια ενότητα περιβάλλοντος χρήστη στην επόμενη, πιέστε το πλήκτρο Tab.

\n' + '\n' + '

Για να μετακινηθείτε από μια ενότητα περιβάλλοντος χρήστη στην προηγούμενη, πιέστε τα πλήκτρα Shift+Tab.

\n' + '\n' + '

Η σειρά Tab αυτών των ενοτήτων περιβάλλοντος χρήστη είναι η εξής:

\n' + '\n' + '
    \n' + '
  1. Γραμμή μενού
  2. \n' + '
  3. Κάθε ομάδα γραμμής εργαλείων
  4. \n' + '
  5. Πλαϊνή γραμμή
  6. \n' + '
  7. Διαδρομή στοιχείου στο υποσέλιδο
  8. \n' + '
  9. Κουμπί εναλλαγής μέτρησης λέξεων στο υποσέλιδο
  10. \n' + '
  11. Σύνδεσμος επωνυμίας στο υποσέλιδο
  12. \n' + '
  13. Λαβή αλλαγής μεγέθους προγράμματος επεξεργασίας στο υποσέλιδο
  14. \n' + '
\n' + '\n' + '

Εάν δεν εμφανίζεται ενότητα περιβάλλοντος χρήστη, παραλείπεται.

\n' + '\n' + '

Εάν η εστίαση πλοήγησης βρίσκεται στο πληκτρολόγιο και δεν υπάρχει εμφανής πλαϊνή γραμμή, εάν πιέσετε Shift+Tab\n' + ' η εστίαση μετακινείται στην πρώτη ομάδα γραμμής εργαλείων, όχι στην τελευταία.

\n' + '\n' + '

Πλοήγηση εντός των ενοτήτων του περιβάλλοντος χρήστη

\n' + '\n' + '

Για να μετακινηθείτε από ένα στοιχείο περιβάλλοντος χρήστη στο επόμενο, πιέστε το αντίστοιχο πλήκτρο βέλους.

\n' + '\n' + '

Με τα πλήκτρα αριστερού και δεξιού βέλους

\n' + '\n' + '\n' + '\n' + '

Με τα πλήκτρα επάνω και κάτω βέλους

\n' + '\n' + '\n' + '\n' + '

Με τα πλήκτρα βέλους γίνεται κυκλική μετακίνηση εντός της εστιασμένης ενότητας περιβάλλοντος χρήστη.

\n' + '\n' + '

Για να κλείσετε ένα ανοιχτό μενού, ένα ανοιχτό υπομενού ή ένα ανοιχτό αναδυόμενο μενού, πιέστε το πλήκτρο Esc.

\n' + '\n' + '

Εάν η τρέχουσα εστίαση βρίσκεται στην κορυφή μιας ενότητας περιβάλλοντος χρήστη, πιέζοντας το πλήκτρο Esc,\n' + ' γίνεται επίσης πλήρης έξοδος από την πλοήγηση μέσω πληκτρολογίου.

\n' + '\n' + '

Εκτέλεση ενός στοιχείου μενού ή κουμπιού γραμμής εργαλείων

\n' + '\n' + '

Όταν το επιθυμητό στοιχείο μενού ή κουμπί γραμμής εργαλείων είναι επισημασμένο, πιέστε τα πλήκτρα Return, Enter,\n' + ' ή το πλήκτρο διαστήματος για να εκτελέσετε το στοιχείο.

\n' + '\n' + '

Πλοήγηση σε παράθυρα διαλόγου χωρίς καρτέλες

\n' + '\n' + '

Σε παράθυρα διαλόγου χωρίς καρτέλες, το πρώτο αλληλεπιδραστικό στοιχείο λαμβάνει την εστίαση όταν ανοίγει το παράθυρο διαλόγου.

\n' + '\n' + '

Μπορείτε να πλοηγηθείτε μεταξύ των αλληλεπιδραστικών στοιχείων παραθύρων διαλόγων πιέζοντας τα πλήκτρα Tab ή Shift+Tab.

\n' + '\n' + '

Πλοήγηση σε παράθυρα διαλόγου με καρτέλες

\n' + '\n' + '

Σε παράθυρα διαλόγου με καρτέλες, το πρώτο κουμπί στο μενού καρτέλας λαμβάνει την εστίαση όταν ανοίγει το παράθυρο διαλόγου.

\n' + '\n' + '

Μπορείτε να πλοηγηθείτε μεταξύ των αλληλεπιδραστικών στοιχείων αυτής της καρτέλα διαλόγου πιέζοντας τα πλήκτρα Tab ή\n' + ' Shift+Tab.

\n' + '\n' + '

Μπορείτε να κάνετε εναλλαγή σε άλλη καρτέλα του παραθύρου διαλόγου, μεταφέροντας την εστίαση στο μενού καρτέλας και πιέζοντας το κατάλληλο πλήκτρο βέλους\n' + ' για να μετακινηθείτε κυκλικά στις διαθέσιμες καρτέλες.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/en.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.en', '

Begin keyboard navigation

\n' + '\n' + '
\n' + '
Focus the Menu bar
\n' + '
Windows or Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Focus the Toolbar
\n' + '
Windows or Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Focus the footer
\n' + '
Windows or Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Focus the notification
\n' + '
Windows or Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Focus a contextual toolbar
\n' + '
Windows, Linux or macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigation will start at the first UI item, which will be highlighted, or underlined in the case of the first item in\n' + ' the Footer element path.

\n' + '\n' + '

Navigate between UI sections

\n' + '\n' + '

To move from one UI section to the next, press Tab.

\n' + '\n' + '

To move from one UI section to the previous, press Shift+Tab.

\n' + '\n' + '

The Tab order of these UI sections is:

\n' + '\n' + '
    \n' + '
  1. Menu bar
  2. \n' + '
  3. Each toolbar group
  4. \n' + '
  5. Sidebar
  6. \n' + '
  7. Element path in the footer
  8. \n' + '
  9. Word count toggle button in the footer
  10. \n' + '
  11. Branding link in the footer
  12. \n' + '
  13. Editor resize handle in the footer
  14. \n' + '
\n' + '\n' + '

If a UI section is not present, it is skipped.

\n' + '\n' + '

If the footer has keyboard navigation focus, and there is no visible sidebar, pressing Shift+Tab\n' + ' moves focus to the first toolbar group, not the last.

\n' + '\n' + '

Navigate within UI sections

\n' + '\n' + '

To move from one UI element to the next, press the appropriate Arrow key.

\n' + '\n' + '

The Left and Right arrow keys

\n' + '\n' + '\n' + '\n' + '

The Down and Up arrow keys

\n' + '\n' + '\n' + '\n' + '

Arrow keys cycle within the focused UI section.

\n' + '\n' + '

To close an open menu, an open sub-menu, or an open pop-up menu, press the Esc key.

\n' + '\n' + '

If the current focus is at the ‘top’ of a particular UI section, pressing the Esc key also exits\n' + ' keyboard navigation entirely.

\n' + '\n' + '

Execute a menu item or toolbar button

\n' + '\n' + '

When the desired menu item or toolbar button is highlighted, press Return, Enter,\n' + ' or the Space bar to execute the item.

\n' + '\n' + '

Navigate non-tabbed dialogs

\n' + '\n' + '

In non-tabbed dialogs, the first interactive component takes focus when the dialog opens.

\n' + '\n' + '

Navigate between interactive dialog components by pressing Tab or Shift+Tab.

\n' + '\n' + '

Navigate tabbed dialogs

\n' + '\n' + '

In tabbed dialogs, the first button in the tab menu takes focus when the dialog opens.

\n' + '\n' + '

Navigate between interactive components of this dialog tab by pressing Tab or\n' + ' Shift+Tab.

\n' + '\n' + '

Switch to another dialog tab by giving the tab menu focus and then pressing the appropriate Arrow\n' + ' key to cycle through the available tabs.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/es.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.es', '

Iniciar la navegación con el teclado

\n' + '\n' + '
\n' + '
Enfocar la barra de menús
\n' + '
Windows o Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Enfocar la barra de herramientas
\n' + '
Windows o Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Enfocar el pie de página
\n' + '
Windows o Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Enfocar la notificación
\n' + '
Windows o Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Enfocar una barra de herramientas contextual
\n' + '
Windows, Linux o macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

La navegación comenzará por el primer elemento de la interfaz de usuario (IU), de tal manera que se resaltará, o bien se subrayará si se trata del primer elemento de\n' + ' la ruta de elemento del pie de página.

\n' + '\n' + '

Navegar entre las secciones de la IU

\n' + '\n' + '

Para pasar de una sección de la IU a la siguiente, pulse la tecla Tab.

\n' + '\n' + '

Para pasar de una sección de la IU a la anterior, pulse Mayús+Tab.

\n' + '\n' + '

El orden de tabulación de estas secciones de la IU es:

\n' + '\n' + '
    \n' + '
  1. Barra de menús
  2. \n' + '
  3. Cada grupo de barra de herramientas
  4. \n' + '
  5. Barra lateral
  6. \n' + '
  7. Ruta del elemento en el pie de página
  8. \n' + '
  9. Botón de alternancia de recuento de palabras en el pie de página
  10. \n' + '
  11. Enlace de personalización de marca en el pie de página
  12. \n' + '
  13. Controlador de cambio de tamaño en el pie de página
  14. \n' + '
\n' + '\n' + '

Si una sección de la IU no está presente, esta se omite.

\n' + '\n' + '

Si el pie de página tiene un enfoque de navegación con el teclado y no hay ninguna barra lateral visible, al pulsar Mayús+Tab,\n' + ' el enfoque se moverá al primer grupo de barra de herramientas, en lugar de al último.

\n' + '\n' + '

Navegar dentro de las secciones de la IU

\n' + '\n' + '

Para pasar de un elemento de la IU al siguiente, pulse la tecla de flecha correspondiente.

\n' + '\n' + '

Las teclas de flecha izquierda y derecha permiten

\n' + '\n' + '\n' + '\n' + '

Las teclas de flecha abajo y arriba permiten

\n' + '\n' + '\n' + '\n' + '

Las teclas de flecha van cambiando dentro de la sección de la IU enfocada.

\n' + '\n' + '

Para cerrar un menú, un submenú o un menú emergente que estén abiertos, pulse la tecla Esc.

\n' + '\n' + '

Si el enfoque actual se encuentra en la parte superior de una sección de la IU determinada, al pulsar la tecla Esc saldrá\n' + ' de la navegación con el teclado por completo.

\n' + '\n' + '

Ejecutar un elemento de menú o un botón de barra de herramientas

\n' + '\n' + '

Si el elemento de menú o el botón de barra de herramientas deseado está resaltado, pulse la tecla Retorno o Entrar,\n' + ' o la barra espaciadora para ejecutar el elemento.

\n' + '\n' + '

Navegar por cuadros de diálogo sin pestañas

\n' + '\n' + '

En los cuadros de diálogo sin pestañas, el primer componente interactivo se enfoca al abrirse el cuadro de diálogo.

\n' + '\n' + '

Para navegar entre los componentes interactivos del cuadro de diálogo, pulse las teclas Tab o Mayús+Tab.

\n' + '\n' + '

Navegar por cuadros de diálogo con pestañas

\n' + '\n' + '

En los cuadros de diálogo con pestañas, el primer botón del menú de pestaña se enfoca al abrirse el cuadro de diálogo.

\n' + '\n' + '

Para navegar entre componentes interactivos de esta pestaña del cuadro de diálogo, pulse las teclas Tab o\n' + ' Mayús+Tab.

\n' + '\n' + '

Si desea cambiar a otra pestaña del cuadro de diálogo, enfoque el menú de pestañas y, a continuación, pulse la tecla de flecha\n' + ' correspondiente para moverse por las pestañas disponibles.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/eu.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.eu', '

Hasi teklatuaren nabigazioa

\n' + '\n' + '
\n' + '
Fokuratu menu-barra
\n' + '
Windows edo Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Fokuratu tresna-barra
\n' + '
Windows edo Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Fokuratu orri-oina
\n' + '
Windows edo Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Fokuratu jakinarazpena
\n' + '
Windows edo Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Fokuratu testuinguruaren tresna-barra
\n' + '
Windows, Linux edo macOS: Ktrl+F9
\n' + '
\n' + '\n' + '

Nabigazioa EIko lehen elementuan hasiko da: elementu hori nabarmendu egingo da, edo azpimarratu lehen elementua bada\n' + ' orri-oineko elementuaren bidea.

\n' + '\n' + '

Nabigatu EIko atalen artean

\n' + '\n' + '

EIko atal batetik hurrengora mugitzeko, sakatu Tabuladorea.

\n' + '\n' + '

EIko atal batetik aurrekora mugitzeko, sakatu Maius+Tabuladorea.

\n' + '\n' + '

EIko atal hauen Tabuladorea da:

\n' + '\n' + '
    \n' + '
  1. Menu-barra
  2. \n' + '
  3. Tresna-barraren talde bakoitza
  4. \n' + '
  5. Alboko barra
  6. \n' + '
  7. Orri-oineko elementuaren bidea
  8. \n' + '
  9. Orri-oneko urrats-kontaketa txandakatzeko botoia
  10. \n' + '
  11. Orri-oineko marken esteka
  12. \n' + '
  13. Orri-oineko editorearen tamaina aldatzeko heldulekua
  14. \n' + '
\n' + '\n' + '

EIko atal bat ez badago, saltatu egin da.

\n' + '\n' + '

Orri-oinak teklatuaren nabigazioa fokuratuta badago, eta alboko barra ikusgai ez badago, Maius+Tabuladorea sakatuz gero,\n' + ' fokua tresna-barrako lehen taldera eramaten da, ez azkenera.

\n' + '\n' + '

Nabigatu EIko atalen barruan

\n' + '\n' + '

EIko elementu batetik hurrengora mugitzeko, sakatu dagokion Gezia tekla.

\n' + '\n' + '

Ezkerrera eta Eskuinera gezi-teklak

\n' + '\n' + '\n' + '\n' + '

Gora eta Behera gezi-teklak

\n' + '\n' + '\n' + '\n' + '

Gezia teklen zikloa nabarmendutako EI atalen barruan.

\n' + '\n' + '

Irekitako menu bat ixteko, ireki azpimenua, edo ireki menu gainerakorra, sakatu Ihes tekla.

\n' + '\n' + '

Une horretan fokuratzea EIko atal jakin baten "goialdean" badago, Ihes tekla sakatuz gero\n' + ' teklatuaren nabigaziotik irtengo zara.

\n' + '\n' + '

Exekutatu menuko elementu bat edo tresna-barrako botoi bat

\n' + '\n' + '

Nahi den menuaren elementua edo tresna-barraren botoia nabarmenduta dagoenean, sakatu Itzuli, Sartu\n' + ' edo Zuriune-barra elementua exekutatzeko.

\n' + '\n' + '

Nabigatu fitxarik gabeko elkarrizketak

\n' + '\n' + '

Fitxarik gabeko elkarrizketetan, lehen osagai interaktiboa fokuratzen da elkarrizketa irekitzen denean.

\n' + '\n' + '

Nabigatu elkarrizketa interaktiboko osagai batetik bestera Tabuladorea edo Maius+Tabuladorea sakatuta.

\n' + '\n' + '

Nabigatu fitxadun elkarrizketak

\n' + '\n' + '

Fitxadun elkarrizketetan, fitxa-menuko lehen botoia fokuratzen da elkarrizketa irekitzen denean.

\n' + '\n' + '

Nabigatu elkarrizketa-fitxa honen interaktiboko osagai batetik bestera Tabuladorea edo\n' + ' Maius+Tabuladorea sakatuta.

\n' + '\n' + '

Aldatu beste elkarrizketa-fitxa batera fitxa-menua fokuratu eta dagokion Gezia\n' + ' tekla sakatzeko, erabilgarri dauden fitxa batetik bestera txandakatzeko.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fa.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.fa', '

شروع پیمایش صفحه‌کلید

\n' + '\n' + '
\n' + '
تمرکز بر نوار منو
\n' + '
Windows یا Linux:‎‏: Alt+F9
\n' + '
‎‏macOS: ⌥F9‎‏
\n' + '
تمرکز بر نوار ابزار
\n' + '
Windows یا Linux‎‏: Alt+F10
\n' + '
‎‏macOS: ⌥F10‎‏
\n' + '
تمرکز بر پانویس
\n' + '
Windows یا Linux‎‏: Alt+F11
\n' + '
‎‏macOS: ⌥F11‎‏
\n' + '
تمرکز اعلان
\n' + '
ویندوز یا لینوکس: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
تمرکز بر نوار ابزار بافتاری
\n' + '
Windows ،Linux یا macOS:‏ Ctrl+F9
\n' + '
\n' + '\n' + '

پیمایش در اولین مورد رابط کاربری شروع می‌شود و درخصوص اولین مورد در\n' + ' مسیر عنصر پانویس، برجسته یا زیرخط‌دار می‌شود.

\n' + '\n' + '

پیمایش بین بخش‌های رابط کاربری

\n' + '\n' + '

برای جابجایی از یک بخش رابط کاربری به بخش بعدی، Tab را فشار دهید.

\n' + '\n' + '

برای جابجایی از یک بخش رابط کاربری به بخش قبلی، Shift+Tab را فشار دهید.

\n' + '\n' + '

ترتیب Tab این بخش‌های رابط کاربری عبارتند از:

\n' + '\n' + '
    \n' + '
  1. نوار منو
  2. \n' + '
  3. هر گروه نوار ابزار
  4. \n' + '
  5. نوار کناری
  6. \n' + '
  7. مسیر عنصر در پانویس
  8. \n' + '
  9. دکمه تغییر وضعیت تعداد کلمات در پانویس
  10. \n' + '
  11. پیوند نمانام‌سازی در پانویس
  12. \n' + '
  13. دسته تغییر اندازه ویرایشگر در پانویس
  14. \n' + '
\n' + '\n' + '

اگر بخشی از رابط کاربری موجود نباشد، رد می‌شود.

\n' + '\n' + '

اگر پانویس دارای تمرکز بر پیمایش صفحه‌کلید باشد،‌ و نوار کناری قابل‌مشاهده وجود ندارد، فشردن Shift+Tab\n' + ' تمرکز را به گروه نوار ابزار اول می‌برد، نه آخر.

\n' + '\n' + '

پیمایش در بخش‌های رابط کاربری

\n' + '\n' + '

برای جابجایی از یک عنصر رابط کاربری به بعدی، کلید جهت‌نمای مناسب را فشار دهید.

\n' + '\n' + '

کلیدهای جهت‌نمای چپ و راست

\n' + '\n' + '\n' + '\n' + '

کلیدهای جهت‌نمای پایین و بالا

\n' + '\n' + '\n' + '\n' + '

کلیدهایجهت‌نما در بخش رابط کاربری متمرکز می‌چرخند.

\n' + '\n' + '

برای بستن یک منوی باز، یک منوی فرعی باز، یا یک منوی بازشوی باز، کلید Esc را فشار دهید.

\n' + '\n' + '

اگر تمرکز فعلی در «بالای» یک بخش رابط کاربری خاص است، فشردن کلید Esc نیز موجب\n' + ' خروج کامل از پیمایش صفحه‌کلید می‌شود.

\n' + '\n' + '

اجرای یک مورد منو یا دکمه نوار ابزار

\n' + '\n' + '

وقتی مورد منو یا دکمه نوار ابزار مورد نظر هایلایت شد، دکمه بازگشت، Enter،\n' + ' یا نوار Space را فشار دهید تا مورد را اجرا کنید.

\n' + '\n' + '

پیمایش در کادرهای گفتگوی بدون زبانه

\n' + '\n' + '

در کادرهای گفتگوی بدون زبانه، وقتی کادر گفتگو باز می‌شود، اولین جزء تعاملی متمرکز می‌شود.

\n' + '\n' + '

با فشردن Tab یا Shift+Tab، بین اجزای کادر گفتگوی تعاملی پیمایش کنید.

\n' + '\n' + '

پیمایش کادرهای گفتگوی زبانه‌دار

\n' + '\n' + '

در کادرهای گفتگوی زبانه‌دار، وقتی کادر گفتگو باز می‌شود، اولین دکمه در منوی زبانه متمرکز می‌شود.

\n' + '\n' + '

با فشردن Tab یا\n' + ' Shift+Tab، بین اجزای تعاملی این زبانه کادر گفتگو پیمایش کنید.

\n' + '\n' + '

با دادن تمرکز به منوی زبانه و سپس فشار دادن کلید جهت‌نمای\n' + ' مناسب برای چرخش میان زبانه‌های موجود، به زبانه کادر گفتگوی دیگری بروید.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fi.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.fi', '

Näppäimistönavigoinnin aloittaminen

\n' + '\n' + '
\n' + '
Siirrä kohdistus valikkopalkkiin
\n' + '
Windows tai Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Siirrä kohdistus työkalupalkkiin
\n' + '
Windows tai Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Siirrä kohdistus alatunnisteeseen
\n' + '
Windows tai Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Keskitä ilmoitukseen
\n' + '
Windows ja Linux: Alt + F12
\n' + '
macOS: ⌥F12
\n' + '
Siirrä kohdistus kontekstuaaliseen työkalupalkkiin
\n' + '
Windows, Linux tai macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigointi aloitetaan ensimmäisestä käyttöliittymän kohteesta, joka joko korostetaan tai alleviivataan, jos\n' + ' kyseessä on Alatunniste-elementin polun ensimmäinen kohde.

\n' + '\n' + '

Käyttöliittymän eri osien välillä navigointi

\n' + '\n' + '

Paina sarkainnäppäintä siirtyäksesi käyttöliittymän osasta seuraavaan.

\n' + '\n' + '

Jos haluat siirtyä edelliseen käyttöliittymän osaan, paina Shift+sarkainnäppäin.

\n' + '\n' + '

Sarkainnäppäin siirtää sinua näissä käyttöliittymän osissa tässä järjestyksessä:

\n' + '\n' + '
    \n' + '
  1. Valikkopalkki
  2. \n' + '
  3. Työkalupalkin ryhmät
  4. \n' + '
  5. Sivupalkki
  6. \n' + '
  7. Elementin polku alatunnisteessa
  8. \n' + '
  9. Sanalaskurin vaihtopainike alatunnisteessa
  10. \n' + '
  11. Brändäyslinkki alatunnisteessa
  12. \n' + '
  13. Editorin koon muuttamisen kahva alatunnisteessa
  14. \n' + '
\n' + '\n' + '

Jos jotakin käyttöliittymän osaa ei ole, se ohitetaan.

\n' + '\n' + '

Jos kohdistus on siirretty alatunnisteeseen näppäimistönavigoinnilla eikä sivupalkkia ole näkyvissä, Shift+sarkainnäppäin\n' + ' siirtää kohdistuksen työkalupalkin ensimmäiseen ryhmään, eikä viimeiseen.

\n' + '\n' + '

Käyttöliittymän eri osien sisällä navigointi

\n' + '\n' + '

Paina nuolinäppäimiä siirtyäksesi käyttöliittymäelementistä seuraavaan.

\n' + '\n' + '

Vasen- ja Oikea-nuolinäppäimet

\n' + '\n' + '\n' + '\n' + '

Alas- ja Ylös-nuolinäppäimet

\n' + '\n' + '\n' + '\n' + '

Nuolinäppäimet siirtävät sinua käyttöliittymän korostetun osan sisällä syklissä.

\n' + '\n' + '

Paina Esc-näppäintä sulkeaksesi avoimen valikon, avataksesi alavalikon tai avataksesi ponnahdusvalikon.

\n' + '\n' + '

Jos kohdistus on käyttöliittymän tietyn osion ylälaidassa, Esc-näppäimen painaminen\n' + ' poistuu myös näppäimistönavigoinnista kokonaan.

\n' + '\n' + '

Suorita valikkokohde tai työkalupalkin painike

\n' + '\n' + '

Kun haluamasi valikkokohde tai työkalupalkin painike on korostettuna, paina Return-, Enter-\n' + ' tai välilyöntinäppäintä suorittaaksesi kohteen.

\n' + '\n' + '

Välilehdittömissä valintaikkunoissa navigointi

\n' + '\n' + '

Kun välilehdetön valintaikkuna avautuu, kohdistus siirtyy sen ensimmäiseen interaktiiviseen komponenttiin.

\n' + '\n' + '

Voit siirtyä valintaikkunan interaktiivisten komponenttien välillä painamalla sarkainnäppäintä tai Shift+sarkainnäppäin.

\n' + '\n' + '

Välilehdellisissä valintaikkunoissa navigointi

\n' + '\n' + '

Kun välilehdellinen valintaikkuna avautuu, kohdistus siirtyy välilehtivalikon ensimmäiseen painikkeeseen.

\n' + '\n' + '

Voit siirtyä valintaikkunan välilehden interaktiivisen komponenttien välillä painamalla sarkainnäppäintä tai\n' + ' Shift+sarkainnäppäin.

\n' + '\n' + '

Voit siirtyä valintaikkunan toiseen välilehteen siirtämällä kohdistuksen välilehtivalikkoon ja painamalla sopivaa nuolinäppäintä\n' + ' siirtyäksesi käytettävissä olevien välilehtien välillä syklissä.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/fr_FR.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.fr_FR', '

Débuter la navigation au clavier

\n' + '\n' + '
\n' + '
Cibler la barre du menu
\n' + '
Windows ou Linux : Alt+F9
\n' + '
macOS : ⌥F9
\n' + "
Cibler la barre d'outils
\n" + '
Windows ou Linux : Alt+F10
\n' + '
macOS : ⌥F10
\n' + '
Cibler le pied de page
\n' + '
Windows ou Linux : Alt+F11
\n' + '
macOS : ⌥F11
\n' + '
Cibler la notification
\n' + '
Windows ou Linux : Alt+F12
\n' + '
macOS : ⌥F12
\n' + "
Cibler une barre d'outils contextuelle
\n" + '
Windows, Linux ou macOS : Ctrl+F9
\n' + '
\n' + '\n' + "

La navigation débutera sur le premier élément de l'interface utilisateur, qui sera mis en surbrillance ou bien souligné dans le cas du premier élément du\n" + " chemin d'éléments du pied de page.

\n" + '\n' + "

Naviguer entre les sections de l'interface utilisateur

\n" + '\n' + "

Pour passer d'une section de l'interface utilisateur à la suivante, appuyez sur Tabulation.

\n" + '\n' + "

Pour passer d'une section de l'interface utilisateur à la précédente, appuyez sur Maj+Tabulation.

\n" + '\n' + "

L'ordre de Tabulation de ces sections de l'interface utilisateur est le suivant :

\n" + '\n' + '
    \n' + '
  1. Barre du menu
  2. \n' + "
  3. Chaque groupe de barres d'outils
  4. \n" + '
  5. Barre latérale
  6. \n' + "
  7. Chemin d'éléments du pied de page
  8. \n" + "
  9. Bouton d'activation du compteur de mots dans le pied de page
  10. \n" + '
  11. Lien de marque dans le pied de page
  12. \n' + "
  13. Poignée de redimensionnement de l'éditeur dans le pied de page
  14. \n" + '
\n' + '\n' + "

Si une section de l'interface utilisateur n'est pas présente, elle sera ignorée.

\n" + '\n' + "

Si le pied de page comporte un ciblage par navigation au clavier et qu'il n'y a aucune barre latérale visible, appuyer sur Maj+Tabulation\n" + " déplace le ciblage vers le premier groupe de barres d'outils et non le dernier.

\n" + '\n' + "

Naviguer au sein des sections de l'interface utilisateur

\n" + '\n' + "

Pour passer d'un élément de l'interface utilisateur au suivant, appuyez sur la Flèche appropriée.

\n" + '\n' + '

Les touches fléchées Gauche et Droite

\n' + '\n' + '\n' + '\n' + '

Les touches fléchées Bas et Haut

\n' + '\n' + '\n' + '\n' + "

Les Flèches parcourent la section de l'interface utilisateur ciblée.

\n" + '\n' + '

Pour fermer un menu ouvert, un sous-menu ouvert ou un menu contextuel ouvert, appuyez sur Echap.

\n' + '\n' + "

Si l'actuel ciblage se trouve en « haut » d'une section spécifique de l'interface utilisateur, appuyer sur Echap permet également de quitter\n" + ' entièrement la navigation au clavier.

\n' + '\n' + "

Exécuter un élément de menu ou un bouton de barre d'outils

\n" + '\n' + "

Lorsque l'élément de menu ou le bouton de barre d'outils désiré est mis en surbrillance, appuyez sur la touche Retour arrière, Entrée\n" + " ou la Barre d'espace pour exécuter l'élément.

\n" + '\n' + '

Naviguer au sein de dialogues sans onglets

\n' + '\n' + "

Dans les dialogues sans onglets, le premier composant interactif est ciblé lorsque le dialogue s'ouvre.

\n" + '\n' + '

Naviguez entre les composants du dialogue interactif en appuyant sur Tabulation ou Maj+Tabulation.

\n' + '\n' + '

Naviguer au sein de dialogues avec onglets

\n' + '\n' + "

Dans les dialogues avec onglets, le premier bouton du menu de l'onglet est ciblé lorsque le dialogue s'ouvre.

\n" + '\n' + '

Naviguez entre les composants interactifs de cet onglet de dialogue en appuyant sur Tabulation ou\n' + ' Maj+Tabulation.

\n' + '\n' + "

Passez à un autre onglet de dialogue en ciblant le menu de l'onglet et en appuyant sur la Flèche\n" + ' appropriée pour parcourir les onglets disponibles.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/he_IL.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.he_IL', '

התחל ניווט במקלדת

\n' + '\n' + '
\n' + '
התמקד בשורת התפריטים
\n' + '
Windows או Linux:‏ Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
העבר מיקוד לסרגל הכלים
\n' + '
Windows או Linux:‏ Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
העבר מיקוד לכותרת התחתונה
\n' + '
Windows או Linux:‏ Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
העבר מיקוד להודעה
\n' + '
Windows או Linux:‏ Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
העבר מיקוד לסרגל כלים הקשרי
\n' + '
Windows‏, Linux או macOS:‏ Ctrl+F9
\n' + '
\n' + '\n' + '

הניווט יתחיל ברכיב הראשון במשך, שיודגש או שיהיה מתחתיו קו תחתון במקרה של הפריט הראשון\n' + ' הנתיב של רכיב הכותרת התחתונה.

\n' + '\n' + '

עבור בין מקטעים במסך

\n' + '\n' + '

כדי לעבור בין המקטעים במסך, הקש Tab.

\n' + '\n' + '

כדי לעבור למקטע הקודם במסך, הקש Shift+Tab.

\n' + '\n' + '

הסדר מבחינת מקש Tab של הרכיבים במסך:

\n' + '\n' + '
    \n' + '
  1. שורת התפריטים
  2. \n' + '
  3. כל קבוצה בסרגל הכלים
  4. \n' + '
  5. הסרגל הצידי
  6. \n' + '
  7. נתיב של רכיב בכותרת התחתונה
  8. \n' + '
  9. לחצן לספירת מילים בכותרת התחתונה
  10. \n' + '
  11. קישור של המותג בכותרת התחתונה
  12. \n' + '
  13. ידית לשינוי גודל עבור העורך בכותרת התחתונה
  14. \n' + '
\n' + '\n' + '

אם רכיב כלשהו במסך לא מופיע, המערכת תדלג עליו.

\n' + '\n' + '

אם בכותרת התחתונה יש מיקוד של ניווט במקלדת, ולא מופיע סרגל בצד, יש להקיש Shift+Tab\n' + ' מעביר את המיקוד לקבוצה הראשונה בסרגל הכלים, לא האחרונה.

\n' + '\n' + '

עבור בתוך מקטעים במסך

\n' + '\n' + '

כדי לעבור מרכיב אחד לרכיב אחר במסך, הקש על מקש החץ המתאים.

\n' + '\n' + '

מקשי החיצים שמאלה וימינה

\n' + '\n' + '\n' + '\n' + '

מקשי החיצים למטה ולמעלה

\n' + '\n' + '\n' + '\n' + '

מקשי החצים משתנים בתוך המקטע במסך שעליו נמצא המיקוד.

\n' + '\n' + '

כדי לסגור תפריט פתוח, תפריט משני פתוח או חלון קופץ, הקש על Esc.

\n' + '\n' + "

אם המיקוד הוא על החלק 'העליון' של מקטע מסוים במסך, הקשה על Esc מביאה גם ליציאה\n" + ' מהניווט במקלדת לחלוטין.

\n' + '\n' + '

הפעל פריט בתפריט או לחצן בסרגל הכלים

\n' + '\n' + '

כאשר הפריט הרצוי בתפריט או הלחצן בסרגל הכלים מודגשים, הקש על Return, Enter,\n' + ' או על מקש הרווח כדי להפעיל את הפריט.

\n' + '\n' + '

ניווט בחלונות דו-שיח בלי כרטיסיות

\n' + '\n' + '

בחלונות דו-שיח בלי כרטיסיות, הרכיב האינטראקטיבי הראשון מקבל את המיקוד כאשר החלון נפתח.

\n' + '\n' + '

עבור בין רכיבים אינטראקטיביים בחלון על ידי הקשה על Tab או Shift+Tab.

\n' + '\n' + '

ניווט בחלונות דו-שיח עם כרטיסיות

\n' + '\n' + '

בחלונות דו-שיח עם כרטיסיות, הלחצן הראשון בתפריט מקבל את המיקוד כאשר החלון נפתח.

\n' + '\n' + '

עבור בין רכיבים אינטראקטיביים בחלון על ידי הקשה על Tab או\n' + ' Shift+Tab.

\n' + '\n' + '

עבור לכרטיסיה אחרת בחלון על ידי העברת המיקוד לתפריט הכרטיסיות והקשה על החץהמתאים\n' + ' כדי לעבור בין הכרטיסיות הזמינות.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hi.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.hi', '

कीबोर्ड नेविगेशन शुरू करें

\n' + '\n' + '
\n' + '
मेन्यू बार पर फ़ोकस करें
\n' + '
Windows या Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
टूलबार पर फ़ोकस करें
\n' + '
Windows या Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
फ़ुटर पर फ़ोकस करें
\n' + '
Windows या Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
नोटिफ़िकेशन फ़ोकस
\n' + '
Windows या Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
प्रासंगिक टूलबार पर फ़ोकस करें
\n' + '
Windows, Linux या macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

नेविगेशन पहले UI आइटम पर शुरू होगा, जिसे हाइलाइट किया जाएगा या पहले आइटम के मामले में फ़ुटर तत्व पथ में\n' + ' रेखांकित किया जाएगा।

\n' + '\n' + '

UI सेक्शन के बीच नेविगेट करें

\n' + '\n' + '

एक UI सेक्शन से दूसरे सेक्शन में जाने के लिए, Tab दबाएं।

\n' + '\n' + '

एक UI सेक्शन से पिछले सेक्शन में जाने के लिए, Shift+Tab दबाएं।

\n' + '\n' + '

इन UI सेक्शन का Tab क्रम नीचे दिया गया है:

\n' + '\n' + '
    \n' + '
  1. मेन्यू बार
  2. \n' + '
  3. प्रत्येक टूलबार समूह
  4. \n' + '
  5. साइडबार
  6. \n' + '
  7. फ़ुटर में तत्व पथ
  8. \n' + '
  9. फ़ुटर में शब्द गणना टॉगल बटन
  10. \n' + '
  11. फ़ुटर में ब्रांडिंग लिंक
  12. \n' + '
  13. फ़ुटर में संपादक का आकार बदलने का हैंडल
  14. \n' + '
\n' + '\n' + '

अगर कोई UI सेक्शन मौजूद नहीं है, तो उसे छोड़ दिया जाता है।

\n' + '\n' + '

अगर फ़ुटर में कीबोर्ड नेविगेशन फ़ोकस है, और कोई दिखा देने वाला साइडबार नहीं है, तो Shift+Tab दबाने से\n' + ' फ़ोकस पहले टूलबार समूह पर चला जाता है, पिछले पर नहीं।

\n' + '\n' + '

UI सेक्शन के भीतर नेविगेट करें

\n' + '\n' + '

एक UI तत्व से दूसरे में जाने के लिए उपयुक्त ऐरो कुंजी दबाएं।

\n' + '\n' + '

बाएं और दाएं ऐरो कुंजियां

\n' + '\n' + '\n' + '\n' + '

नीचे और ऊपर ऐरो कुंजियां

\n' + '\n' + '\n' + '\n' + '

फ़ोकस वाले UI सेक्शन के भीतर ऐरो कुंजियां चलाती रहती हैं।

\n' + '\n' + '

कोई खुला मेन्यू, कोई खुला सब-मेन्यू या कोई खुला पॉप-अप मेन्यू बंद करने के लिए Esc कुंजी दबाएं।

\n' + '\n' + "

अगर मौजूदा फ़ोकस किसी विशेष UI सेक्शन के 'शीर्ष' पर है, तो Esc कुंजी दबाने से भी\n" + ' कीबोर्ड नेविगेशन पूरी तरह से बाहर हो जाता है।

\n' + '\n' + '

मेन्यू आइटम या टूलबार बटन निष्पादित करें

\n' + '\n' + '

जब वांछित मेन्यू आइटम या टूलबार बटन हाइलाइट किया जाता है, तो आइटम को निष्पादित करने के लिए Return, Enter,\n' + ' या Space bar दबाएं।

\n' + '\n' + '

गैर-टैब वाले डायलॉग पर नेविगेट करें

\n' + '\n' + '

गैर-टैब वाले डायलॉग में, डायलॉग खुलने पर पहला इंटरैक्टिव घटक फ़ोकस लेता है।

\n' + '\n' + '

Tab or Shift+Tab दबाकर इंटरैक्टिव डायलॉग घटकों के बीच नेविगेट करें।

\n' + '\n' + '

टैब किए गए डायलॉग पर नेविगेट करें

\n' + '\n' + '

टैब किए गए डायलॉग में, डायलॉग खुलने पर टैब मेन्यू में पहला बटन फ़ोकस लेता है।

\n' + '\n' + '

इस डायलॉग टैब के इंटरैक्टिव घटकों के बीच नेविगेट करने के लिए Tab या\n' + ' Shift+Tab दबाएं।

\n' + '\n' + '

टैब मेन्यू को फ़ोकस देकर और फिर उपलब्ध टैब में के बीच जाने के लिए उपयुक्त ऐरो\n' + ' कुंजी दबाकर दूसरे डायलॉग टैब पर स्विच करें।

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hr.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.hr', '

Početak navigacije na tipkovnici

\n' + '\n' + '
\n' + '
Fokusiranje trake izbornika
\n' + '
Windows ili Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Fokusiranje alatne trake
\n' + '
Windows ili Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Fokusiranje podnožja
\n' + '
Windows ili Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Fokusiranje obavijesti
\n' + '
Windows ili Linux: Alt + F12
\n' + '
macOS: ⌥F12
\n' + '
Fokusiranje kontekstne alatne trake
\n' + '
Windows, Linux ili macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigacija će započeti kod prve stavke na korisničkom sučelju, koja će biti istaknuta ili podcrtana ako se radi o prvoj stavci u\n' + ' putu elementa u podnožju.

\n' + '\n' + '

Navigacija između dijelova korisničkog sučelja

\n' + '\n' + '

Za pomicanje s jednog dijela korisničkog sučelja na drugi pritisnite tabulator.

\n' + '\n' + '

Za pomicanje s jednog dijela korisničkog sučelja na prethodni pritisnite Shift + tabulator.

\n' + '\n' + '

Ovo je redoslijed pomicanja tabulatora po dijelovima korisničkog sučelja:

\n' + '\n' + '
    \n' + '
  1. Traka izbornika
  2. \n' + '
  3. Pojedinačne grupe na alatnoj traci
  4. \n' + '
  5. Bočna traka
  6. \n' + '
  7. Put elemenata u podnožju
  8. \n' + '
  9. Gumb za pomicanje po broju riječi u podnožju
  10. \n' + '
  11. Veza na brand u podnožju
  12. \n' + '
  13. Značajka za promjenu veličine alata za uređivanje u podnožju
  14. \n' + '
\n' + '\n' + '

Ako neki dio korisničkog sučelja nije naveden, on se preskače.

\n' + '\n' + '

Ako u podnožju postoji fokus za navigaciju na tipkovnici, a nema vidljive bočne trake, pritiskom na Shift + tabulator\n' + ' fokus se prebacuje na prvu skupinu na alatnoj traci, ne na zadnju.

\n' + '\n' + '

Navigacija unutar dijelova korisničkog sučelja

\n' + '\n' + '

Za pomicanje s jednog elementa korisničkog sučelja na drugi pritisnite tipku s odgovarajućom strelicom.

\n' + '\n' + '

Tipke s lijevom i desnom strelicom

\n' + '\n' + '\n' + '\n' + '

Tipke s donjom i gornjom strelicom

\n' + '\n' + '\n' + '\n' + '

Tipkama strelica kružno se pomičete unutar dijela korisničkog sučelja koji je u fokusu.

\n' + '\n' + '

Za zatvaranje otvorenog izbornika, otvorenog podizbornika ili otvorenog skočnog izbornika pritisnite tipku Esc.

\n' + '\n' + '

Ako je fokus trenutačno postavljen na vrh pojedinačnog dijela korisničkog sučelja, pritiskom na tipku Esc također\n' + ' u potpunosti zatvarate navigaciju na tipkovnici.

\n' + '\n' + '

Izvršavanje radnji putem stavki izbornika ili gumba na alatnoj traci

\n' + '\n' + '

Nakon što se istakne stavka izbornika ili gumb na alatnoj traci s radnjom koju želite izvršiti, pritisnite tipku Return, Enter\n' + ' ili razmak da biste pokrenuli željenu radnju.

\n' + '\n' + '

Navigacija dijaloškim okvirima izvan kartica

\n' + '\n' + '

Prilikom otvaranja dijaloških okvira izvan kartica fokus se nalazi na prvoj interaktivnoj komponenti.

\n' + '\n' + '

Navigaciju između interaktivnih dijaloških komponenata vršite pritiskom na tabulator ili Shift + tabulator.

\n' + '\n' + '

Navigacija dijaloškim okvirima u karticama

\n' + '\n' + '

Prilikom otvaranja dijaloških okvira u karticama fokus se nalazi na prvom gumbu u izborniku unutar kartice.

\n' + '\n' + '

Navigaciju između interaktivnih komponenata dijaloškog okvira u kartici vršite pritiskom na tabulator ili\n' + ' Shift + tabulator.

\n' + '\n' + '

Na karticu s drugim dijaloškim okvirom možete se prebaciti tako da stavite fokus na izbornik kartice pa pritisnete tipku s odgovarajućom strelicom\n' + ' za kružno pomicanje između dostupnih kartica.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/hu_HU.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.hu_HU', '

Billentyűzetes navigáció indítása

\n' + '\n' + '
\n' + '
Fókusz a menüsávra
\n' + '
Windows és Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Fókusz az eszköztárra
\n' + '
Windows és Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Fókusz a láblécre
\n' + '
Windows és Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Ráközelítés az értesítésre
\n' + '
Windows vagy Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Fókusz egy környezetfüggő eszköztárra
\n' + '
Windows, Linux és macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

A navigáció az első felhasználói felületi elemnél kezdődik, amelyet a rendszer kiemel, illetve aláhúz, amennyiben az az első elem\n' + ' a lábléc elemútvonalán.

\n' + '\n' + '

Navigálás a felhasználói felület szakaszai között

\n' + '\n' + '

A felhasználói felület következő szakaszára váltáshoz nyomja meg a Tab billentyűt.

\n' + '\n' + '

A felhasználói felület előző szakaszára váltáshoz nyomja meg a Shift+Tab billentyűt.

\n' + '\n' + '

A Tab billentyűvel a felhasználói felület szakaszai között a következő sorrendben vált:

\n' + '\n' + '
    \n' + '
  1. Menüsáv
  2. \n' + '
  3. Az egyes eszköztárcsoportok
  4. \n' + '
  5. Oldalsáv
  6. \n' + '
  7. Elemútvonal a láblécen
  8. \n' + '
  9. Szószámátkapcsoló gomb a láblécen
  10. \n' + '
  11. Márkalink a láblécen
  12. \n' + '
  13. Szerkesztő átméretezési fogópontja a láblécen
  14. \n' + '
\n' + '\n' + '

Ha a felhasználói felület valamelyik eleme nincs jelen, a rendszer kihagyja.

\n' + '\n' + '

Ha a billentyűzetes navigáció fókusza a láblécen van, és nincs látható oldalsáv, a Shift+Tab\n' + ' billentyűkombináció lenyomásakor az első eszköztárcsoportra ugrik a fókusz, nem az utolsóra.

\n' + '\n' + '

Navigálás a felhasználói felület szakaszain belül

\n' + '\n' + '

A felhasználói felület következő elemére váltáshoz nyomja meg a megfelelő nyílbillentyűt.

\n' + '\n' + '

A bal és a jobb nyílgomb

\n' + '\n' + '\n' + '\n' + '

A le és a fel nyílgomb

\n' + '\n' + '\n' + '\n' + '

A nyílbillentyűk lenyomásával körkörösen lépkedhet a fókuszban lévő felhasználói felületi szakasz elemei között.

\n' + '\n' + '

A megnyitott menüket, almenüket és előugró menüket az Esc billentyűvel zárhatja be.

\n' + '\n' + '

Ha a fókusz az aktuális felületi elem „felső” részén van, az Esc billentyűvel az egész\n' + ' billentyűzetes navigációból kilép.

\n' + '\n' + '

Menüpont vagy eszköztárgomb aktiválása

\n' + '\n' + '

Amikor a kívánt menüelem vagy eszköztárgomb van kijelölve, nyomja meg a Return, az Enter\n' + ' vagy a Szóköz billentyűt az adott elem vagy gomb aktiválásához.

\n' + '\n' + '

Navigálás a lapokkal nem rendelkező párbeszédablakokban

\n' + '\n' + '

A lapokkal nem rendelkező párbeszédablakokban az első interaktív összetevő kapja a fókuszt, amikor a párbeszédpanel megnyílik.

\n' + '\n' + '

A párbeszédpanelek interaktív összetevői között a Tab vagy a Shift+Tab billentyűvel navigálhat.

\n' + '\n' + '

Navigálás a lapokkal rendelkező párbeszédablakokban

\n' + '\n' + '

A lapokkal rendelkező párbeszédablakokban a lapmenü első gombja kapja a fókuszt, amikor a párbeszédpanel megnyílik.

\n' + '\n' + '

A párbeszédpanel e lapjának interaktív összetevői között a Tab vagy\n' + ' Shift+Tab billentyűvel navigálhat.

\n' + '\n' + '

A párbeszédablak másik lapjára úgy léphet, hogy a fókuszt a lapmenüre állítja, majd lenyomja a megfelelő nyílbillentyűt\n' + ' a rendelkezésre álló lapok közötti lépkedéshez.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/id.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.id', '

Memulai navigasi keyboard

\n' + '\n' + '
\n' + '
Fokus pada bilah Menu
\n' + '
Windows atau Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Fokus pada Bilah Alat
\n' + '
Windows atau Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Fokus pada footer
\n' + '
Windows atau Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Fokuskan pemberitahuan
\n' + '
Windows atau Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Fokus pada bilah alat kontekstual
\n' + '
Windows, Linux, atau macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigasi akan dimulai dari item pertama UI, yang akan disorot atau digarisbawahi di\n' + ' alur elemen Footer.

\n' + '\n' + '

Berpindah antar-bagian UI

\n' + '\n' + '

Untuk berpindah dari satu bagian UI ke bagian berikutnya, tekan Tab.

\n' + '\n' + '

Untuk berpindah dari satu bagian UI ke bagian sebelumnya, tekan Shift+Tab.

\n' + '\n' + '

Urutan Tab bagian-bagian UI ini adalah:

\n' + '\n' + '
    \n' + '
  1. Bilah menu
  2. \n' + '
  3. Tiap grup bilah alat
  4. \n' + '
  5. Bilah sisi
  6. \n' + '
  7. Alur elemen di footer
  8. \n' + '
  9. Tombol aktifkan/nonaktifkan jumlah kata di footer
  10. \n' + '
  11. Tautan merek di footer
  12. \n' + '
  13. Pengatur pengubahan ukuran editor di footer
  14. \n' + '
\n' + '\n' + '

Jika suatu bagian UI tidak ada, bagian tersebut dilewati.

\n' + '\n' + '

Jika fokus navigasi keyboard ada pada footer, tetapi tidak ada bilah sisi yang terlihat, menekan Shift+Tab\n' + ' akan memindahkan fokus ke grup bilah alat pertama, bukan yang terakhir.

\n' + '\n' + '

Berpindah di dalam bagian-bagian UI

\n' + '\n' + '

Untuk berpindah dari satu elemen UI ke elemen berikutnya, tekan tombol Panah yang sesuai.

\n' + '\n' + '

Tombol panah Kiri dan Kanan untuk

\n' + '\n' + '\n' + '\n' + '

Tombol panah Bawah dan Atas untuk

\n' + '\n' + '\n' + '\n' + '

Tombol Panah hanya bergerak di dalam bagian UI yang difokuskan.

\n' + '\n' + '

Untuk menutup menu, sub-menu, atau menu pop-up yang terbuka, tekan tombol Esc.

\n' + '\n' + '

Jika fokus sedang berada di ‘atas’ bagian UI tertentu, menekan tombol Esc juga dapat mengeluarkan fokus\n' + ' dari seluruh navigasi keyboard.

\n' + '\n' + '

Menjalankan item menu atau tombol bilah alat

\n' + '\n' + '

Jika item menu atau tombol bilah alat yang diinginkan tersorot, tekan Return, Enter,\n' + ' atau Spasi untuk menjalankan item.

\n' + '\n' + '

Berpindah dalam dialog tanpa tab

\n' + '\n' + '

Dalam dialog tanpa tab, fokus diarahkan pada komponen interaktif pertama saat dialog terbuka.

\n' + '\n' + '

Berpindah di antara komponen dalam dialog interaktif dengan menekan Tab atau Shift+Tab.

\n' + '\n' + '

Berpindah dalam dialog dengan tab

\n' + '\n' + '

Dalam dialog yang memiliki tab, fokus diarahkan pada tombol pertama di dalam menu saat dialog terbuka.

\n' + '\n' + '

Berpindah di antara komponen-komponen interaktif pada tab dialog ini dengan menekan Tab atau\n' + ' Shift+Tab.

\n' + '\n' + '

Beralih ke tab dialog lain dengan mengarahkan fokus pada menu tab lalu tekan tombol Panah\n' + ' yang sesuai untuk berpindah ke berbagai tab yang tersedia.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/it.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.it', '

Iniziare la navigazione tramite tastiera

\n' + '\n' + '
\n' + '
Impostare lo stato attivo per la barra dei menu
\n' + '
Windows o Linux: ALT+F9
\n' + '
macOS: ⌥F9
\n' + '
Impostare lo stato attivo per la barra degli strumenti
\n' + '
Windows o Linux: ALT+F10
\n' + '
macOS: ⌥F10
\n' + '
Impostare lo stato attivo per il piè di pagina
\n' + '
Windows o Linux: ALT+F11
\n' + '
macOS: ⌥F11
\n' + '
Metti a fuoco la notifica
\n' + '
Windows o Linux: ALT+F12
\n' + '
macOS: ⌥F12
\n' + '
Impostare lo stato attivo per la barra degli strumenti contestuale
\n' + '
Windows, Linux o macOS: CTRL+F9
\n' + '
\n' + '\n' + "

La navigazione inizierà dalla prima voce dell'interfaccia utente, che sarà evidenziata o sottolineata nel caso della prima voce\n" + " nel percorso dell'elemento del piè di pagina.

\n" + '\n' + "

Navigare tra le sezioni dell'interfaccia utente

\n" + '\n' + "

Per passare da una sezione dell'interfaccia utente alla successiva, premere TAB.

\n" + '\n' + "

Per passare da una sezione dell'interfaccia utente alla precedente, premere MAIUSC+TAB.

\n" + '\n' + "

L'ordine di tabulazione di queste sezioni dell'interfaccia utente è:

\n" + '\n' + '
    \n' + '
  1. Barra dei menu
  2. \n' + '
  3. Ogni gruppo di barre degli strumenti
  4. \n' + '
  5. Barra laterale
  6. \n' + "
  7. Percorso dell'elemento nel piè di pagina
  8. \n" + '
  9. Pulsante di attivazione/disattivazione del conteggio delle parole nel piè di pagina
  10. \n' + '
  11. Collegamento al marchio nel piè di pagina
  12. \n' + "
  13. Quadratino di ridimensionamento dell'editor nel piè di pagina
  14. \n" + '
\n' + '\n' + "

Se una sezione dell'interfaccia utente non è presente, viene saltata.

\n" + '\n' + '

Se il piè di pagina ha lo stato attivo per la navigazione tramite tastiera e non è presente alcuna barra laterale visibile, premendo MAIUSC+TAB\n' + " si sposta lo stato attivo sul primo gruppo di barre degli strumenti, non sull'ultimo.

\n" + '\n' + "

Navigare all'interno delle sezioni dell'interfaccia utente

\n" + '\n' + "

Per passare da un elemento dell'interfaccia utente al successivo, premere il tasto freccia appropriato.

\n" + '\n' + '

I tasti freccia Sinistra e Destra

\n' + '\n' + '\n' + '\n' + '

I tasti freccia Giù e Su

\n' + '\n' + '\n' + '\n' + "

I tasti freccia consentono di spostarsi all'interno della sezione dell'interfaccia utente con stato attivo.

\n" + '\n' + '

Per chiudere un menu aperto, un sottomenu aperto o un menu a comparsa aperto, premere il tasto ESC.

\n' + '\n' + "

Se lo stato attivo corrente si trova nella parte superiore di una particolare sezione dell'interfaccia utente, premendo il tasto ESC si esce\n" + ' completamente dalla navigazione tramite tastiera.

\n' + '\n' + '

Eseguire una voce di menu o un pulsante della barra degli strumenti

\n' + '\n' + '

Quando la voce di menu o il pulsante della barra degli strumenti desiderati sono evidenziati, premere il tasto diritorno a capo, il tasto Invio\n' + ' o la barra spaziatrice per eseguirli.

\n' + '\n' + '

Navigare nelle finestre di dialogo non a schede

\n' + '\n' + "

Nelle finestre di dialogo non a schede, all'apertura della finestra di dialogo diventa attivo il primo componente interattivo.

\n" + '\n' + '

Per spostarsi tra i componenti interattivi della finestra di dialogo, premere TAB o MAIUSC+TAB.

\n' + '\n' + '

Navigare nelle finestre di dialogo a schede

\n' + '\n' + "

Nelle finestre di dialogo a schede, all'apertura della finestra di dialogo diventa attivo il primo pulsante del menu della scheda.

\n" + '\n' + '

Per spostarsi tra i componenti interattivi di questa scheda della finestra di dialogo, premere TAB o\n' + ' MAIUSC+TAB.

\n' + '\n' + "

Per passare a un'altra scheda della finestra di dialogo, attivare il menu della scheda e premere il tasto freccia\n" + ' appropriato per scorrere le schede disponibili.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ja.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.ja', '

キーボード ナビゲーションの開始

\n' + '\n' + '
\n' + '
メニュー バーをフォーカス
\n' + '
Windows または Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
ツール バーをフォーカス
\n' + '
Windows または Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
フッターをフォーカス
\n' + '
Windows または Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
通知にフォーカス
\n' + '
Windows または Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
コンテキスト ツール バーをフォーカス
\n' + '
Windows、Linux または macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

ナビゲーションは最初の UI 項目から開始され、強調表示されるか、フッターの要素パスにある最初の項目の場合は\n' + ' 下線が引かれます。

\n' + '\n' + '

UI セクション間の移動

\n' + '\n' + '

次の UI セクションに移動するには、Tab を押します。

\n' + '\n' + '

前の UI セクションに移動するには、Shift+Tab を押します。

\n' + '\n' + '

これらの UI セクションの Tab の順序:

\n' + '\n' + '
    \n' + '
  1. メニュー バー
  2. \n' + '
  3. 各ツール バー グループ
  4. \n' + '
  5. サイド バー
  6. \n' + '
  7. フッターの要素パス
  8. \n' + '
  9. フッターの単語数切り替えボタン
  10. \n' + '
  11. フッターのブランド リンク
  12. \n' + '
  13. フッターのエディター サイズ変更ハンドル
  14. \n' + '
\n' + '\n' + '

UI セクションが存在しない場合は、スキップされます。

\n' + '\n' + '

フッターにキーボード ナビゲーション フォーカスがあり、表示可能なサイド バーがない場合、Shift+Tab を押すと、\n' + ' フォーカスが最後ではなく最初のツール バー グループに移動します。

\n' + '\n' + '

UI セクション内の移動

\n' + '\n' + '

次の UI 要素に移動するには、適切な矢印キーを押します。

\n' + '\n' + '

左矢印右矢印のキー

\n' + '\n' + '\n' + '\n' + '

下矢印上矢印のキー

\n' + '\n' + '\n' + '\n' + '

矢印キーで、フォーカスされた UI セクション内で循環します。

\n' + '\n' + '

開いたメニュー、開いたサブメニュー、開いたポップアップ メニューを閉じるには、Esc キーを押します。

\n' + '\n' + '

現在のフォーカスが特定の UI セクションの「一番上」にある場合、Esc キーを押すと\n' + ' キーボード ナビゲーションも完全に閉じられます。

\n' + '\n' + '

メニュー項目またはツール バー ボタンの実行

\n' + '\n' + '

目的のメニュー項目やツール バー ボタンが強調表示されている場合、リターンEnter、\n' + ' またはスペース キーを押して項目を実行します。

\n' + '\n' + '

タブのないダイアログの移動

\n' + '\n' + '

タブのないダイアログでは、ダイアログが開くと最初の対話型コンポーネントがフォーカスされます。

\n' + '\n' + '

Tab または Shift+Tab を押して、対話型ダイアログ コンポーネント間で移動します。

\n' + '\n' + '

タブ付きダイアログの移動

\n' + '\n' + '

タブ付きダイアログでは、ダイアログが開くとタブ メニューの最初のボタンがフォーカスされます。

\n' + '\n' + '

Tab または\n' + ' Shift+Tab を押して、このダイアログ タブの対話型コンポーネント間で移動します。

\n' + '\n' + '

タブ メニューをフォーカスしてから適切な矢印キーを押して表示可能なタブを循環して、\n' + ' 別のダイアログに切り替えます。

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/kk.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.kk', '

Пернетақта навигациясын бастау

\n' + '\n' + '
\n' + '
Мәзір жолағын фокустау
\n' + '
Windows немесе Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Құралдар тақтасын фокустау
\n' + '
Windows немесе Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Төменгі деректемені фокустау
\n' + '
Windows немесе Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Хабарландыруды белгілеу
\n' + '
Windows немесе Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Мәтінмәндік құралдар тақтасын фокустау
\n' + '
Windows, Linux немесе macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Навигация бөлектелетін немесе Төменгі деректеме элементінің жолындағы бірінші элемент жағдайында асты сызылатын\n' + ' бірінші ПИ элементінен басталады.

\n' + '\n' + '

ПИ бөлімдері арасында навигациялау

\n' + '\n' + '

Бір ПИ бөлімінен келесісіне өту үшін Tab пернесін басыңыз.

\n' + '\n' + '

Бір ПИ бөлімінен алдыңғысына өту үшін Shift+Tab пернесін басыңыз.

\n' + '\n' + '

Осы ПИ бөлімдерінің Tab реті:

\n' + '\n' + '
    \n' + '
  1. Мәзір жолағы
  2. \n' + '
  3. Әрбір құралдар тақтасы тобы
  4. \n' + '
  5. Бүйірлік жолақ
  6. \n' + '
  7. Төменгі деректемедегі элемент жолы
  8. \n' + '
  9. Төменгі деректемедегі сөздер санын ауыстыру түймесі
  10. \n' + '
  11. Төменгі деректемедегі брендингтік сілтеме
  12. \n' + '
  13. Төменгі деректемедегі редактор өлшемін өзгерту тұтқасы
  14. \n' + '
\n' + '\n' + '

ПИ бөлімі көрсетілмесе, ол өткізіп жіберіледі.

\n' + '\n' + '

Төменгі деректемеде пернетақта навигациясының фокусы болса және бүйірлік жолақ көрінбесе, Shift+Tab тіркесімін басу әрекеті\n' + ' фокусты соңғысы емес, бірінші құралдар тақтасы тобына жылжытады.

\n' + '\n' + '

ПИ бөлімдерінде навигациялау

\n' + '\n' + '

Бір ПИ элементінен келесісіне өту үшін Arrow (Көрсеткі) пернесін басыңыз.

\n' + '\n' + '

Left (Сол жақ) және Right (Оң жақ) көрсеткі пернелері

\n' + '\n' + '\n' + '\n' + '

Down (Төмен) және Up (Жоғары) көрсеткі пернелері

\n' + '\n' + '\n' + '\n' + '

Фокусталған ПИ бөліміндегі Arrow (Көрсеткі) пернелерінің циклі.

\n' + '\n' + '

Ашық мәзірді жабу үшін ішкі мәзірді ашып немесе ашылмалы мәзірді ашып, Esc пернесін басыңыз.

\n' + '\n' + '

Ағымдағы фокус белгілі бір ПИ бөлімінің «үстінде» болса, Esc пернесін басу әрекеті пернетақта\n' + ' навигациясын толығымен жабады.

\n' + '\n' + '

Мәзір элементін немесе құралдар тақтасы түймесін орындау

\n' + '\n' + '

Қажетті мәзір элементі немесе құралдар тақтасы түймесі бөлектелген кезде, элементті орындау үшін Return (Қайтару), Enter (Енгізу)\n' + ' немесе Space bar (Бос орын) пернесін басыңыз.

\n' + '\n' + '

Белгіленбеген диалог терезелерін навигациялау

\n' + '\n' + '

Белгіленбеген диалог терезелерінде диалог терезесі ашылған кезде бірінші интерактивті құрамдас фокусталады.

\n' + '\n' + '

Tab немесе Shift+Tab пернесін басу арқылы интерактивті диалог терезесінің құрамдастары арасында навигациялаңыз.

\n' + '\n' + '

Белгіленген диалог терезелерін навигациялау

\n' + '\n' + '

Белгіленген диалог терезелерінде диалог терезесі ашылған кезде қойынды мәзіріндегі бірінші түйме фокусталады.

\n' + '\n' + '

Tab немесе\n' + ' Shift+Tab пернесін басу арқылы осы диалог терезесі қойындысының интерактивті құрамдастары арасында навигациялаңыз.

\n' + '\n' + '

Қойынды мәзірінің фокусын беру арқылы басқа диалог терезесінің қойындысына ауысып, тиісті Arrow (Көрсеткі)\n' + ' пернесін басу арқылы қолжетімді қойындылар арасында айналдыруға болады.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ko_KR.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.ko_KR', '

키보드 탐색 시작

\n' + '\n' + '
\n' + '
메뉴 모음 포커스 표시
\n' + '
Windows 또는 Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
도구 모음 포커스 표시
\n' + '
Windows 또는 Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
푸터 포커스 표시
\n' + '
Windows 또는 Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
알림 포커스
\n' + '
Windows 또는 Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
컨텍스트 도구 모음에 포커스 표시
\n' + '
Windows, Linux 또는 macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

첫 번째 UI 항목에서 탐색이 시작되며, 이때 첫 번째 항목이 강조 표시되거나 푸터 요소 경로에 있는\n' + ' 경우 밑줄 표시됩니다.

\n' + '\n' + '

UI 섹션 간 탐색

\n' + '\n' + '

한 UI 섹션에서 다음 UI 섹션으로 이동하려면 Tab(탭)을 누릅니다.

\n' + '\n' + '

한 UI 섹션에서 이전 UI 섹션으로 돌아가려면 Shift+Tab(시프트+탭)을 누릅니다.

\n' + '\n' + '

이 UI 섹션의 Tab(탭) 순서는 다음과 같습니다.

\n' + '\n' + '
    \n' + '
  1. 메뉴 바
  2. \n' + '
  3. 각 도구 모음 그룹
  4. \n' + '
  5. 사이드바
  6. \n' + '
  7. 푸터의 요소 경로
  8. \n' + '
  9. 푸터의 단어 수 토글 버튼
  10. \n' + '
  11. 푸터의 브랜딩 링크
  12. \n' + '
  13. 푸터의 에디터 크기 변경 핸들
  14. \n' + '
\n' + '\n' + '

UI 섹션이 없는 경우 건너뛰기합니다.

\n' + '\n' + '

푸터에 키보드 탐색 포커스가 있고 사이드바는 보이지 않는 경우 Shift+Tab(시프트+탭)을 누르면\n' + ' 포커스 표시가 마지막이 아닌 첫 번째 도구 모음 그룹으로 이동합니다.

\n' + '\n' + '

UI 섹션 내 탐색

\n' + '\n' + '

한 UI 요소에서 다음 UI 요소로 이동하려면 적절한 화살표 키를 누릅니다.

\n' + '\n' + '

왼쪽오른쪽 화살표 키의 용도:

\n' + '\n' + '\n' + '\n' + '

아래 화살표 키의 용도:

\n' + '\n' + '\n' + '\n' + '

화살표 키는 포커스 표시 UI 섹션 내에서 순환됩니다.

\n' + '\n' + '

열려 있는 메뉴, 열려 있는 하위 메뉴 또는 열려 있는 팝업 메뉴를 닫으려면 Esc 키를 누릅니다.

\n' + '\n' + "

현재 포커스 표시가 특정 UI 섹션 '상단'에 있는 경우 이때도 Esc 키를 누르면\n" + ' 키보드 탐색이 완전히 종료됩니다.

\n' + '\n' + '

메뉴 항목 또는 도구 모음 버튼 실행

\n' + '\n' + '

원하는 메뉴 항목 또는 도구 모음 버튼이 강조 표시되어 있을 때 Return(리턴), Enter(엔터),\n' + ' 또는 Space bar(스페이스바)를 눌러 해당 항목을 실행합니다.

\n' + '\n' + '

탭이 없는 대화 탐색

\n' + '\n' + '

탭이 없는 대화의 경우, 첫 번째 대화형 요소가 포커스 표시된 상태로 대화가 열립니다.

\n' + '\n' + '

대화형 요소들 사이를 이동할 때는 Tab(탭) 또는 Shift+Tab(시프트+탭)을 누릅니다.

\n' + '\n' + '

탭이 있는 대화 탐색

\n' + '\n' + '

탭이 있는 대화의 경우, 탭 메뉴에서 첫 번째 버튼이 포커스 표시된 상태로 대화가 열립니다.

\n' + '\n' + '

이 대화 탭의 대화형 요소들 사이를 이동할 때는 Tab(탭) 또는\n' + ' Shift+Tab(시프트+탭)을 누릅니다.

\n' + '\n' + '

다른 대화 탭으로 이동하려면 탭 메뉴를 포커스 표시한 다음 적절한 화살표\n' + ' 키를 눌러 사용 가능한 탭들을 지나 원하는 탭으로 이동합니다.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ms.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.ms', '

Mulakan navigasi papan kekunci

\n' + '\n' + '
\n' + '
Fokus bar Menu
\n' + '
Windows atau Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Fokus Bar Alat
\n' + '
Windows atau Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Fokus pengaki
\n' + '
Windows atau Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Tumpu kepada pemberitahuan
\n' + '
Windows atau Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Fokus bar alat kontekstual
\n' + '
Windows, Linux atau macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigasi akan bermula pada item UI pertama, yang akan diserlahkan atau digaris bawah dalam saiz item pertama dalam\n' + ' laluan elemen Pengaki.

\n' + '\n' + '

Navigasi antara bahagian UI

\n' + '\n' + '

Untuk bergerak dari satu bahagian UI ke yang seterusnya, tekan Tab.

\n' + '\n' + '

Untuk bergerak dari satu bahagian UI ke yang sebelumnya, tekan Shift+Tab.

\n' + '\n' + '

Tertib Tab bahagian UI ini ialah:

\n' + '\n' + '
    \n' + '
  1. Bar menu
  2. \n' + '
  3. Setiap kumpulan bar alat
  4. \n' + '
  5. Bar sisi
  6. \n' + '
  7. Laluan elemen dalam pengaki
  8. \n' + '
  9. Butang togol kiraan perkataan dalam pengaki
  10. \n' + '
  11. Pautan penjenamaan dalam pengaki
  12. \n' + '
  13. Pemegang saiz semula editor dalam pengaki
  14. \n' + '
\n' + '\n' + '

Jika bahagian UI tidak wujud, ia dilangkau.

\n' + '\n' + '

Jika pengaki mempunyai fokus navigasi papan kekunci dan tiada bar sisi kelihatan, menekan Shift+Tab\n' + ' akan mengalihkan fokus ke kumpulan bar alat pertama, bukannya yang terakhir.

\n' + '\n' + '

Navigasi dalam bahagian UI

\n' + '\n' + '

Untuk bergerak dari satu elemen UI ke yang seterusnya, tekan kekunci Anak Panah yang bersesuaian.

\n' + '\n' + '

Kekunci anak panah Kiri dan Kanan

\n' + '\n' + '\n' + '\n' + '

Kekunci anak panah Bawah dan Atas

\n' + '\n' + '\n' + '\n' + '

Kekunci Anak Panah berkitar dalam bahagian UI difokuskan.

\n' + '\n' + '

Untuk menutup menu buka, submenu terbuka atau menu timbul terbuka, tekan kekunci Esc.

\n' + '\n' + "

Jika fokus semasa berada di bahagian 'atas' bahagian UI tertentu, menekan kekunci Esc juga akan keluar daripada\n" + ' navigasi papan kekunci sepenuhnya.

\n' + '\n' + '

Laksanakan item menu atau butang bar alat

\n' + '\n' + '

Apabila item menu atau butang bar alat yang diinginkan diserlahkan, tekan Return, Enter,\n' + ' atau bar Space untuk melaksanakan item.

\n' + '\n' + '

Navigasi ke dialog tidak bertab

\n' + '\n' + '

Dalam dialog tidak bertab, komponen interaksi pertama difokuskan apabila dialog dibuka.

\n' + '\n' + '

Navigasi antara komponen dialog interaktif dengan menekan Tab atau Shift+Tab.

\n' + '\n' + '

Navigasi ke dialog bertab

\n' + '\n' + '

Dalam dialog bertab, butang pertama dalam menu tab difokuskan apabila dialog dibuka.

\n' + '\n' + '

Navigasi antara komponen interaktif tab dialog ini dengan menekan Tab atau\n' + ' Shift+Tab.

\n' + '\n' + '

Tukar kepada tab dialog lain dengan memfokuskan menu tab, kemudian menekan kekunci Anak Panah yang bersesuaian\n' + ' untuk berkitar menerusi tab yang tersedia.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nb_NO.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.nb_NO', '

Starte tastaturnavigering

\n' + '\n' + '
\n' + '
Utheve menylinjen
\n' + '
Windows eller Linux: Alt + F9
\n' + '
macOS: ⌥F9
\n' + '
Utheve verktøylinjen
\n' + '
Windows eller Linux: Alt + F10
\n' + '
macOS: ⌥F10
\n' + '
Utheve bunnteksten
\n' + '
Windows eller Linux: Alt + F11
\n' + '
macOS: ⌥F11
\n' + '
Fokuser på varselet
\n' + '
Windows eller Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Utheve en kontekstuell verktøylinje
\n' + '
Windows, Linux eller macOS: Ctrl + F9
\n' + '
\n' + '\n' + '

Navigeringen starter ved det første grensesnittelementet, som utheves, eller understrekes når det gjelder det første elementet i\n' + ' elementstien i bunnteksten.

\n' + '\n' + '

Navigere mellom grensesnittdeler

\n' + '\n' + '

Du kan bevege deg fra én grensesnittdel til den neste ved å trykke på tabulatortasten.

\n' + '\n' + '

Du kan bevege deg fra én grensesnittdel til den forrige ved å trykke på Shift + tabulatortasten.

\n' + '\n' + '

Rekkefølgen til tabulatortasten gjennom grensesnittdelene er:

\n' + '\n' + '
    \n' + '
  1. Menylinjen
  2. \n' + '
  3. Hver gruppe på verktøylinjen
  4. \n' + '
  5. Sidestolpen
  6. \n' + '
  7. Elementstien i bunnteksten
  8. \n' + '
  9. Veksleknappen for ordantall i bunnteksten
  10. \n' + '
  11. Merkelenken i bunnteksten
  12. \n' + '
  13. Skaleringshåndtaket for redigeringsprogrammet i bunnteksten
  14. \n' + '
\n' + '\n' + '

Hvis en grensesnittdel ikke er til stede, blir den hoppet over.

\n' + '\n' + '

Hvis tastaturnavigeringen har uthevet bunnteksten og det ikke finnes en synlig sidestolpe, kan du trykke på Shift + tabulatortasten\n' + ' for å flytte fokuset til den første gruppen på verktøylinjen i stedet for den siste.

\n' + '\n' + '

Navigere innenfor grensesnittdeler

\n' + '\n' + '

Du kan bevege deg fra ett grensesnittelement til det neste ved å trykke på den aktuelle piltasten.

\n' + '\n' + '

De venstre og høyre piltastene

\n' + '\n' + '\n' + '\n' + '

Ned- og opp-piltastene

\n' + '\n' + '\n' + '\n' + '

Med piltastene kan du bevege deg innenfor den uthevede grensesnittdelen.

\n' + '\n' + '

Du kan lukke en åpen meny, en åpen undermeny eller en åpen hurtigmeny ved å klikke på Esc-tasten.

\n' + '\n' + '

Hvis det øverste nivået i en grensesnittdel er uthevet, kan du ved å trykke på Esc også avslutte\n' + ' tastaturnavigeringen helt.

\n' + '\n' + '

Utføre et menyelement eller en knapp på en verktøylinje

\n' + '\n' + '

Når det ønskede menyelementet eller verktøylinjeknappen er uthevet, trykker du på Retur, Enter,\n' + ' eller mellomromstasten for å utføre elementet.

\n' + '\n' + '

Navigere i dialogbokser uten faner

\n' + '\n' + '

I dialogbokser uten faner blir den første interaktive komponenten uthevet når dialogboksen åpnes.

\n' + '\n' + '

Naviger mellom interaktive komponenter i dialogboksen ved å trykke på tabulatortasten eller Shift + tabulatortasten.

\n' + '\n' + '

Navigere i fanebaserte dialogbokser

\n' + '\n' + '

I fanebaserte dialogbokser blir den første knappen i fanemenyen uthevet når dialogboksen åpnes.

\n' + '\n' + '

Naviger mellom interaktive komponenter i fanen ved å trykke på tabulatortasten eller\n' + ' Shift + tabulatortasten.

\n' + '\n' + '

Veksle til en annen fane i dialogboksen ved å utheve fanemenyen, og trykk deretter på den aktuelle piltasten\n' + ' for å bevege deg mellom de tilgjengelige fanene.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/nl.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.nl', '

Toetsenbordnavigatie starten

\n' + '\n' + '
\n' + '
Focus op de menubalk instellen
\n' + '
Windows of Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Focus op de werkbalk instellen
\n' + '
Windows of Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Focus op de voettekst instellen
\n' + '
Windows of Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Focus op de melding instellen
\n' + '
Windows of Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Focus op een contextuele werkbalk instellen
\n' + '
Windows, Linux of macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

De navigatie start bij het eerste UI-item, dat wordt gemarkeerd of onderstreept als het eerste item zich in\n' + ' in het elementenpad van de voettekst bevindt.

\n' + '\n' + '

Navigeren tussen UI-secties

\n' + '\n' + '

Druk op Tab om naar de volgende UI-sectie te gaan.

\n' + '\n' + '

Druk op Shift+Tab om naar de vorige UI-sectie te gaan.

\n' + '\n' + '

De Tab-volgorde van deze UI-secties is:

\n' + '\n' + '
    \n' + '
  1. Menubalk
  2. \n' + '
  3. Elke werkbalkgroep
  4. \n' + '
  5. Zijbalk
  6. \n' + '
  7. Elementenpad in de voettekst
  8. \n' + '
  9. Wisselknop voor aantal woorden in de voettekst
  10. \n' + '
  11. Merkkoppeling in de voettekst
  12. \n' + '
  13. Greep voor het wijzigen van het formaat van de editor in de voettekst
  14. \n' + '
\n' + '\n' + '

Als een UI-sectie niet aanwezig is, wordt deze overgeslagen.

\n' + '\n' + '

Als de focus van de toetsenbordnavigatie is ingesteld op de voettekst en er geen zichtbare zijbalk is, kun je op Shift+Tab drukken\n' + ' om de focus naar de eerste werkbalkgroep in plaats van de laatste te verplaatsen.

\n' + '\n' + '

Navigeren binnen UI-secties

\n' + '\n' + '

Druk op de pijltjestoets om naar het betreffende UI-element te gaan.

\n' + '\n' + '

Met de pijltjestoetsen Links en Rechts

\n' + '\n' + '\n' + '\n' + '

Met de pijltjestoetsen Omlaag en Omhoog

\n' + '\n' + '\n' + '\n' + '

Met de pijltjestoetsen wissel je binnen de UI-sectie waarop de focus is ingesteld.

\n' + '\n' + '

Druk op de toets Esc om een geopend menu, submenu of pop-upmenu te sluiten.

\n' + '\n' + "

Als de huidige focus is ingesteld 'bovenaan' een bepaalde UI-sectie, kun je op de toets Esc drukken\n" + ' om de toetsenbordnavigatie af te sluiten.

\n' + '\n' + '

Een menu-item of werkbalkknop uitvoeren

\n' + '\n' + '

Als het gewenste menu-item of de gewenste werkbalkknop is gemarkeerd, kun je op Return, Enter\n' + ' of de spatiebalk drukken om het item uit te voeren.

\n' + '\n' + '

Navigeren in dialoogvensters zonder tabblad

\n' + '\n' + '

Als een dialoogvenster zonder tabblad wordt geopend, wordt de focus ingesteld op het eerste interactieve onderdeel.

\n' + '\n' + '

Je kunt navigeren tussen interactieve onderdelen van een dialoogvenster door op Tab of Shift+Tab te drukken.

\n' + '\n' + '

Navigeren in dialoogvensters met tabblad

\n' + '\n' + '

Als een dialoogvenster met tabblad wordt geopend, wordt de focus ingesteld op de eerste knop in het tabbladmenu.

\n' + '\n' + '

Je kunt navigeren tussen interactieve onderdelen van dit tabblad van het dialoogvenster door op Tab of\n' + ' Shift+Tab te drukken.

\n' + '\n' + '

Je kunt overschakelen naar een ander tabblad van het dialoogvenster door de focus in te stellen op het tabbladmenu en vervolgens op de juiste pijltjestoets\n' + ' te drukken om tussen de beschikbare tabbladen te wisselen.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pl.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.pl', '

Początek nawigacji przy użyciu klawiatury

\n' + '\n' + '
\n' + '
Ustaw fokus na pasek menu
\n' + '
Windows lub Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Ustaw fokus na pasek narzędzi
\n' + '
Windows lub Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Ustaw fokus na sekcję Footer
\n' + '
Windows lub Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Skup się na powiadomieniu
\n' + '
Windows lub Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Ustaw fokus na kontekstowy pasek narzędzi
\n' + '
Windows, Linux lub macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Nawigacja zostanie rozpoczęta od pierwszego elementu interfejsu użytkownika, który jest podświetlony lub — w przypadku pierwszego elementu\n' + ' w ścieżce elementów w sekcji Footer — podkreślony.

\n' + '\n' + '

Nawigacja pomiędzy sekcjami interfejsu użytkownika

\n' + '\n' + '

Aby przenieść się z danej sekcji interfejsu użytkownika do następnej, naciśnij Tab.

\n' + '\n' + '

Aby przenieść się z danej sekcji interfejsu użytkownika do poprzedniej, naciśnij Shift+Tab.

\n' + '\n' + '

Kolejność klawisza Tab w takich sekcjach interfejsu użytkownika jest następująca:

\n' + '\n' + '
    \n' + '
  1. Pasek menu
  2. \n' + '
  3. Każda grupa na pasku narzędzi
  4. \n' + '
  5. Pasek boczny
  6. \n' + '
  7. Ścieżka elementów w sekcji Footer
  8. \n' + '
  9. Przycisk przełączania liczby słów w sekcji Footer
  10. \n' + '
  11. Łącze brandujące w sekcji Footer
  12. \n' + '
  13. Uchwyt zmiany rozmiaru edytora w sekcji Footer
  14. \n' + '
\n' + '\n' + '

Jeżeli nie ma sekcji interfejsu użytkownika, jest to pomijane.

\n' + '\n' + '

Jeżeli na sekcji Footer jest ustawiony fokus nawigacji przy użyciu klawiatury i nie ma widocznego paska bocznego, naciśnięcie Shift+Tab\n' + ' przenosi fokus na pierwszą grupę paska narzędzi, a nie na ostatnią.

\n' + '\n' + '

Nawigacja wewnątrz sekcji interfejsu użytkownika

\n' + '\n' + '

Aby przenieść się z danego elementu interfejsu użytkownika do następnego, naciśnij odpowiedni klawisz strzałki.

\n' + '\n' + '

Klawisze strzałek w prawo i w lewo służą do

\n' + '\n' + '\n' + '\n' + '

Klawisze strzałek w dół i w górę służą do

\n' + '\n' + '\n' + '\n' + '

Klawisze strzałek służą do przemieszczania się w sekcji interfejsu użytkownika z ustawionym fokusem.

\n' + '\n' + '

Aby zamknąć otwarte menu, otwarte podmenu lub otwarte menu wyskakujące, naciśnij klawisz Esc.

\n' + '\n' + '

Jeżeli fokus jest ustawiony na górze konkretnej sekcji interfejsu użytkownika, naciśnięcie klawisza Esc powoduje wyjście\n' + ' z nawigacji przy użyciu klawiatury.

\n' + '\n' + '

Wykonanie elementu menu lub przycisku paska narzędzi

\n' + '\n' + '

Gdy podświetlony jest żądany element menu lub przycisk paska narzędzi, naciśnij klawisz Return, Enter\n' + ' lub Spacja, aby go wykonać.

\n' + '\n' + '

Nawigacja po oknie dialogowym bez kart

\n' + '\n' + '

Gdy otwiera się okno dialogowe bez kart, fokus ustawiany jest na pierwszą interaktywną część okna.

\n' + '\n' + '

Pomiędzy interaktywnymi częściami okna dialogowego nawiguj, naciskając klawisze Tab lub Shift+Tab.

\n' + '\n' + '

Nawigacja po oknie dialogowym z kartami

\n' + '\n' + '

W przypadku okna dialogowego z kartami po otwarciu okna dialogowego fokus ustawiany jest na pierwszy przycisk w menu karty.

\n' + '\n' + '

Nawigację pomiędzy interaktywnymi częściami karty okna dialogowego prowadzi się poprzez naciskanie klawiszy Tab lub\n' + ' Shift+Tab.

\n' + '\n' + '

Przełączenie się na inną kartę okna dialogowego wykonuje się poprzez ustawienie fokusu na menu karty i naciśnięcie odpowiedniego klawisza strzałki\n' + ' w celu przemieszczenia się pomiędzy dostępnymi kartami.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_BR.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.pt_BR', '

Iniciar navegação pelo teclado

\n' + '\n' + '
\n' + '
Foco na barra de menus
\n' + '
Windows ou Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Foco na barra de ferramentas
\n' + '
Windows ou Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Foco no rodapé
\n' + '
Windows ou Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Foco na notificação
\n' + '
Windows ou Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Foco na barra de ferramentas contextual
\n' + '
Windows, Linux ou macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

A navegação inicia no primeiro item da IU, que será destacado ou sublinhado no caso do primeiro item no\n' + ' caminho do elemento Rodapé.

\n' + '\n' + '

Navegar entre seções da IU

\n' + '\n' + '

Para ir de uma seção da IU para a seguinte, pressione Tab.

\n' + '\n' + '

Para ir de uma seção da IU para a anterior, pressione Shift+Tab.

\n' + '\n' + '

A ordem de Tab destas seções da IU é:

\n' + '\n' + '
    \n' + '
  1. Barra de menus
  2. \n' + '
  3. Cada grupo da barra de ferramentas
  4. \n' + '
  5. Barra lateral
  6. \n' + '
  7. Caminho do elemento no rodapé
  8. \n' + '
  9. Botão de alternar contagem de palavras no rodapé
  10. \n' + '
  11. Link da marca no rodapé
  12. \n' + '
  13. Alça de redimensionamento do editor no rodapé
  14. \n' + '
\n' + '\n' + '

Se não houver uma seção da IU, ela será pulada.

\n' + '\n' + '

Se o rodapé tiver o foco da navegação pelo teclado e não houver uma barra lateral visível, pressionar Shift+Tab\n' + ' move o foco para o primeiro grupo da barra de ferramentas, não para o último.

\n' + '\n' + '

Navegar dentro das seções da IU

\n' + '\n' + '

Para ir de um elemento da IU para o seguinte, pressione a Seta correspondente.

\n' + '\n' + '

As teclas de seta Esquerda e Direita

\n' + '\n' + '\n' + '\n' + '

As teclas de seta Abaixo e Acima

\n' + '\n' + '\n' + '\n' + '

As teclas de Seta alternam dentre a seção da IU em foco.

\n' + '\n' + '

Para fechar um menu aberto, um submenu aberto ou um menu suspenso aberto, pressione Esc.

\n' + '\n' + '

Se o foco atual estiver no ‘alto’ de determinada seção da IU, pressionar Esc também sai\n' + ' totalmente da navegação pelo teclado.

\n' + '\n' + '

Executar um item de menu ou botão da barra de ferramentas

\n' + '\n' + '

Com o item de menu ou botão da barra de ferramentas desejado destacado, pressione Return, Enter,\n' + ' ou a Barra de espaço para executar o item.

\n' + '\n' + '

Navegar por caixas de diálogo sem guias

\n' + '\n' + '

Em caixas de diálogo sem guias, o primeiro componente interativo recebe o foco quando a caixa de diálogo abre.

\n' + '\n' + '

Navegue entre componentes interativos de caixa de diálogo pressionando Tab ou Shift+Tab.

\n' + '\n' + '

Navegar por caixas de diálogo com guias

\n' + '\n' + '

Em caixas de diálogo com guias, o primeiro botão no menu da guia recebe o foco quando a caixa de diálogo abre.

\n' + '\n' + '

Navegue entre componentes interativos dessa guia da caixa de diálogo pressionando Tab ou\n' + ' Shift+Tab.

\n' + '\n' + '

Alterne para outra guia da caixa de diálogo colocando o foco no menu da guia e pressionando a Seta\n' + ' adequada para percorrer as guias disponíveis.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/pt_PT.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.pt_PT', '

Iniciar navegação com teclado

\n' + '\n' + '
\n' + '
Foco na barra de menu
\n' + '
Windows ou Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Foco na barra de ferramentas
\n' + '
Windows ou Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Foco no rodapé
\n' + '
Windows ou Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Focar a notificação
\n' + '
Windows ou Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Foco numa barra de ferramentas contextual
\n' + '
Windows, Linux ou macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

A navegação começará no primeiro item de IU, que estará realçado ou sublinhado, no caso do primeiro item no\n' + ' caminho do elemento do rodapé.

\n' + '\n' + '

Navegar entre secções de IU

\n' + '\n' + '

Para se mover de uma secção de IU para a seguinte, prima Tab.

\n' + '\n' + '

Para se mover de uma secção de IU para a anterior, prima Shift+Tab.

\n' + '\n' + '

A ordem de tabulação destas secções de IU é:

\n' + '\n' + '
    \n' + '
  1. Barra de menu
  2. \n' + '
  3. Cada grupo da barra de ferramentas
  4. \n' + '
  5. Barra lateral
  6. \n' + '
  7. Caminho do elemento no rodapé
  8. \n' + '
  9. Botão de alternar da contagem de palavras no rodapé
  10. \n' + '
  11. Ligação da marca no rodapé
  12. \n' + '
  13. Alça de redimensionamento do editor no rodapé
  14. \n' + '
\n' + '\n' + '

Se uma secção de IU não estiver presente, é ignorada.

\n' + '\n' + '

Se o rodapé tiver foco de navegação com teclado e não existir uma barra lateral visível, premir Shift+Tab\n' + ' move o foco para o primeiro grupo da barra de ferramentas e não para o último.

\n' + '\n' + '

Navegar nas secções de IU

\n' + '\n' + '

Para se mover de um elemento de IU para o seguinte, prima a tecla de seta adequada.

\n' + '\n' + '

As teclas de seta Para a esquerda e Para a direita

\n' + '\n' + '\n' + '\n' + '

As teclas de seta Para cima e Para baixo

\n' + '\n' + '\n' + '\n' + '

As teclas de seta deslocam-se ciclicamente na secção de IU em foco.

\n' + '\n' + '

Para fechar um menu aberto, um submenu aberto ou um menu de pop-up aberto, prima a tecla Esc.

\n' + '\n' + '

Se o foco atual estiver no "topo" de determinada secção de IU, premir a tecla Esc também fecha\n' + ' completamente a navegação com teclado.

\n' + '\n' + '

Executar um item de menu ou botão da barra de ferramentas

\n' + '\n' + '

Quando o item de menu ou o botão da barra de ferramentas pretendido estiver realçado, prima Retrocesso, Enter\n' + ' ou a Barra de espaço para executar o item.

\n' + '\n' + '

Navegar em diálogos sem separadores

\n' + '\n' + '

Nos diálogos sem separadores, o primeiro componente interativo fica em foco quando o diálogo abre.

\n' + '\n' + '

Navegue entre componentes interativos do diálogo, premindo Tab ou Shift+Tab.

\n' + '\n' + '

Navegar em diálogos com separadores

\n' + '\n' + '

Nos diálogos com separadores, o primeiro botão no menu do separador fica em foco quando o diálogo abre.

\n' + '\n' + '

Navegue entre os componentes interativos deste separador do diálogo, premindo Tab ou\n' + ' Shift+Tab.

\n' + '\n' + '

Mude para outro separador do diálogo colocando o menu do separador em foco e, em seguida, premindo a tecla de seta\n' + ' adequada para se deslocar ciclicamente pelos separadores disponíveis.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ro.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.ro', '

Începeți navigarea de la tastatură

\n' + '\n' + '
\n' + '
Focalizare pe bara de meniu
\n' + '
Windows sau Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Focalizare pe bara de instrumente
\n' + '
Windows sau Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Focalizare pe subsol
\n' + '
Windows sau Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Focalizare pe notificare
\n' + '
Windows sau Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Focalizare pe o bară de instrumente contextuală
\n' + '
Windows, Linux sau macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigarea va începe de la primul element al interfeței cu utilizatorul, care va fi evidențiat sau subliniat în cazul primului element din\n' + ' calea elementului Subsol.

\n' + '\n' + '

Navigați între secțiunile interfeței cu utilizatorul

\n' + '\n' + '

Pentru a trece de la o secțiune a interfeței cu utilizatorul la alta, apăsați Tab.

\n' + '\n' + '

Pentru a trece de la o secțiune a interfeței cu utilizatorul la cea anterioară, apăsați Shift+Tab.

\n' + '\n' + '

Ordinea cu Tab a acestor secțiuni ale interfeței cu utilizatorul este următoarea:

\n' + '\n' + '
    \n' + '
  1. Bara de meniu
  2. \n' + '
  3. Fiecare grup de bare de instrumente
  4. \n' + '
  5. Bara laterală
  6. \n' + '
  7. Calea elementului în subsol
  8. \n' + '
  9. Buton de comutare a numărului de cuvinte în subsol
  10. \n' + '
  11. Link de branding în subsol
  12. \n' + '
  13. Mâner de redimensionare a editorului în subsol
  14. \n' + '
\n' + '\n' + '

În cazul în care o secțiune a interfeței cu utilizatorul nu este prezentă, aceasta este omisă.

\n' + '\n' + '

În cazul în care subsolul are focalizarea navigației asupra tastaturii și nu există o bară laterală vizibilă, apăsarea butonului Shift+Tab\n' + ' mută focalizarea pe primul grup de bare de instrumente, nu pe ultimul.

\n' + '\n' + '

Navigați în secțiunile interfeței cu utilizatorul

\n' + '\n' + '

Pentru a trece de la un element de interfață cu utilizatorul la următorul, apăsați tasta cu săgeata corespunzătoare.

\n' + '\n' + '

Tastele cu săgeți către stânga și dreapta

\n' + '\n' + '\n' + '\n' + '

Tastele cu săgeți în sus și în jos

\n' + '\n' + '\n' + '\n' + '

Tastele cu săgeți navighează în cadrul secțiunii interfeței cu utilizatorul asupra căreia se focalizează.

\n' + '\n' + '

Pentru a închide un meniu deschis, un sub-meniu deschis sau un meniu pop-up deschis, apăsați tasta Esc.

\n' + '\n' + '

Dacă focalizarea curentă este asupra „părții superioare” a unei anumite secțiuni a interfeței cu utilizatorul, prin apăsarea tastei Esc se iese, de asemenea,\n' + ' în întregime din navigarea de la tastatură.

\n' + '\n' + '

Executarea unui element de meniu sau a unui buton din bara de instrumente

\n' + '\n' + '

Atunci când elementul de meniu dorit sau butonul dorit din bara de instrumente este evidențiat, apăsați Return, Enter,\n' + ' sau bara de spațiu pentru a executa elementul.

\n' + '\n' + '

Navigarea de dialoguri fără file

\n' + '\n' + '

În dialogurile fără file, prima componentă interactivă beneficiază de focalizare la deschiderea dialogului.

\n' + '\n' + '

Navigați între componentele dialogului interactiv apăsând Tab sau Shift+Tab.

\n' + '\n' + '

Navigarea de dialoguri cu file

\n' + '\n' + '

În dialogurile cu file, primul buton din meniul cu file beneficiază de focalizare la deschiderea dialogului.

\n' + '\n' + '

Navigați între componentele interactive ale acestei file de dialog apăsând Tab sau\n' + ' Shift+Tab.

\n' + '\n' + '

Treceți la o altă filă de dialog focalizând asupra meniului cu file și apoi apăsând săgeata corespunzătoare\n' + ' pentru a parcurge filele disponibile.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/ru.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.ru', '

Начните управление с помощью клавиатуры

\n' + '\n' + '
\n' + '
Фокус на панели меню
\n' + '
Windows или Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Фокус на панели инструментов
\n' + '
Windows или Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Фокус на нижнем колонтитуле
\n' + '
Windows или Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Фокус на уведомлении
\n' + '
Windows или Linux: Alt+12
\n' + '
macOS: ⌥F12
\n' + '
Фокус на контекстной панели инструментов
\n' + '
Windows, Linux или macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Первый доступный для управления элемент интерфейса будет выделен цветом или подчеркнут (если он находится\n' + ' в пути элементов нижнего колонтитула).

\n' + '\n' + '

Переход между разделами пользовательского интерфейса

\n' + '\n' + '

Чтобы перейти из текущего раздела интерфейса в следующий, нажмите Tab.

\n' + '\n' + '

Чтобы перейти из текущего раздела интерфейса в предыдущий, нажмите Shift+Tab.

\n' + '\n' + '

Вкладки разделов интерфейса расположены в следующем порядке:

\n' + '\n' + '
    \n' + '
  1. Панель меню
  2. \n' + '
  3. Группы панели инструментов
  4. \n' + '
  5. Боковая панель
  6. \n' + '
  7. Путь элементов нижнего колонтитула
  8. \n' + '
  9. Подсчет слов/символов в нижнем колонтитуле
  10. \n' + '
  11. Брендовая ссылка в нижнем колонтитуле
  12. \n' + '
  13. Угол для изменения размера окна редактора
  14. \n' + '
\n' + '\n' + '

Если раздел интерфейса отсутствует, он пропускается.

\n' + '\n' + '

Если при управлении с клавиатуры фокус находится на нижнем колонтитуле, а видимая боковая панель отсутствует, то при нажатии сочетания клавиш Shift+Tab\n' + ' фокус переносится на первую группу панели инструментов, а не на последнюю.

\n' + '\n' + '

Переход между элементами внутри разделов пользовательского интерфейса

\n' + '\n' + '

Чтобы перейти от текущего элемента интерфейса к следующему, нажмите соответствующую клавишу со стрелкой.

\n' + '\n' + '

Клавиши со стрелками влево и вправо позволяют

\n' + '\n' + '\n' + '\n' + '

Клавиши со стрелками вниз и вверх позволяют

\n' + '\n' + '\n' + '\n' + '

При использовании клавиш со стрелками вы будете циклически перемещаться по элементам в пределах выбранного раздела интерфейса.

\n' + '\n' + '

Чтобы закрыть открытое меню, его раздел или всплывающее меню, нажмите клавишу Esc.

\n' + '\n' + '

Если фокус находится наверху какого-либо раздела интерфейса, нажатие клавиши Esc также приведет\n' + ' к выходу из режима управления с помощью клавиатуры.

\n' + '\n' + '

Использование элемента меню или кнопки на панели инструментов

\n' + '\n' + '

Когда элемент меню или кнопка панели инструментов будут выделены, нажмите Return, Enter\n' + ' или Space, чтобы их активировать.

\n' + '\n' + '

Управление в диалоговом окне без вкладок

\n' + '\n' + '

При открытии диалогового окна без вкладок фокус переносится на первый интерактивный компонент.

\n' + '\n' + '

Для перехода между интерактивными компонентами диалогового окна нажимайте Tab или Shift+Tab.

\n' + '\n' + '

Управление в диалоговом окне с вкладками

\n' + '\n' + '

При открытии диалогового окна с вкладками фокус переносится на первую кнопку в меню вкладок.

\n' + '\n' + '

Для перехода между интерактивными компонентами этой вкладки диалогового окна нажимайте Tab или\n' + ' Shift+Tab.

\n' + '\n' + '

Для перехода на другую вкладку диалогового окна переместите фокус на меню вкладок, а затем используйте клавиши со стрелками\n' + ' для циклического переключения между доступными вкладками.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sk.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.sk', '

Začíname s navigáciou pomocou klávesnice

\n' + '\n' + '
\n' + '
Prejsť na panel s ponukami
\n' + '
Windows alebo Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Prejsť na panel nástrojov
\n' + '
Windows alebo Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Prejsť na pätičku
\n' + '
Windows alebo Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Zaostriť na oznámenie
\n' + '
Windows alebo Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Prejsť na kontextový panel nástrojov
\n' + '
Windows, Linux alebo macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigácia začne pri prvej položke používateľského rozhrania, ktorá bude zvýraznená alebo v prípade prvej položky\n' + ' cesty k pätičke podčiarknutá.

\n' + '\n' + '

Navigácia medzi časťami používateľského rozhrania

\n' + '\n' + '

Ak sa chcete posunúť z jednej časti používateľského rozhrania do druhej, stlačte tlačidlo Tab.

\n' + '\n' + '

Ak sa chcete posunúť z jednej časti používateľského rozhrania do predchádzajúcej, stlačte tlačidlá Shift + Tab.

\n' + '\n' + '

Poradie prepínania medzi týmito časťami používateľského rozhrania pri stláčaní tlačidla Tab:

\n' + '\n' + '
    \n' + '
  1. Panel s ponukou
  2. \n' + '
  3. Každá skupina panela nástrojov
  4. \n' + '
  5. Bočný panel
  6. \n' + '
  7. Cesta k prvku v pätičke
  8. \n' + '
  9. Prepínač počtu slov v pätičke
  10. \n' + '
  11. Odkaz na informácie o značke v pätičke
  12. \n' + '
  13. Úchyt na zmenu veľkosti editora v pätičke
  14. \n' + '
\n' + '\n' + '

Ak nejaká časť používateľského rozhrania nie je prítomná, preskočí sa.

\n' + '\n' + '

Ak je pätička vybratá na navigáciu pomocou klávesnice a nie je viditeľný bočný panel, stlačením klávesov Shift+Tab\n' + ' prejdete na prvú skupinu panela nástrojov, nie na poslednú.

\n' + '\n' + '

Navigácia v rámci častí používateľského rozhrania

\n' + '\n' + '

Ak sa chcete posunúť z jedného prvku používateľského rozhrania na ďalší, stlačte príslušný kláves so šípkou.

\n' + '\n' + '

Klávesy so šípkami doľava a doprava

\n' + '\n' + '\n' + '\n' + '

Klávesy so šípkami dole a hore

\n' + '\n' + '\n' + '\n' + '

Klávesy so šípkami vykonávajú prepínanie v rámci vybranej časti používateľského rozhrania.

\n' + '\n' + '

Ak chcete zatvoriť otvorenú ponuku, otvorenú podponuku alebo otvorenú kontextovú ponuku, stlačte kláves Esc.

\n' + '\n' + '

Ak je aktuálne vybratá horná časť konkrétneho používateľského rozhrania, stlačením klávesu Esc úplne ukončíte tiež\n' + ' navigáciu pomocou klávesnice.

\n' + '\n' + '

Vykonanie príkazu položky ponuky alebo tlačidla panela nástrojov

\n' + '\n' + '

Keď je zvýraznená požadovaná položka ponuky alebo tlačidlo panela nástrojov, stlačením klávesov Return, Enter\n' + ' alebo medzerníka vykonáte príslušný príkaz položky.

\n' + '\n' + '

Navigácia v dialógových oknách bez záložiek

\n' + '\n' + '

Pri otvorení dialógových okien bez záložiek prejdete na prvý interaktívny komponent.

\n' + '\n' + '

Medzi interaktívnymi dialógovými komponentmi môžete prechádzať stlačením klávesov Tab alebo Shift+Tab.

\n' + '\n' + '

Navigácia v dialógových oknách so záložkami

\n' + '\n' + '

Pri otvorení dialógových okien so záložkami prejdete na prvé tlačidlo v ponuke záložiek.

\n' + '\n' + '

Medzi interaktívnymi komponentmi tejto dialógovej záložky môžete prechádzať stlačením klávesov Tab alebo\n' + ' Shift+Tab.

\n' + '\n' + '

Ak chcete prepnúť na ďalšiu záložku dialógového okna, prejdite do ponuky záložiek a potom môžete stlačením príslušného klávesu so šípkou\n' + ' prepínať medzi dostupnými záložkami.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sl_SI.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.sl_SI', '

Začetek krmarjenja s tipkovnico

\n' + '\n' + '
\n' + '
Fokus na menijsko vrstico
\n' + '
Windows ali Linux: Alt + F9
\n' + '
macOS: ⌥F9
\n' + '
Fokus na orodno vrstico
\n' + '
Windows ali Linux: Alt + F10
\n' + '
macOS: ⌥F10
\n' + '
Fokus na nogo
\n' + '
Windows ali Linux: Alt + F11
\n' + '
macOS: ⌥F11
\n' + '
Označitev obvestila
\n' + '
Windows ali Linux: Alt + F12
\n' + '
macOS: ⌥F12
\n' + '
Fokus na kontekstualno orodno vrstico
\n' + '
Windows, Linux ali macOS: Ctrl + F9
\n' + '
\n' + '\n' + '

Krmarjenje se bo začelo s prvim elementom uporabniškega vmesnika, ki bo izpostavljena ali podčrtan, če gre za prvi element na\n' + ' poti do elementa noge.

\n' + '\n' + '

Krmarjenje med razdelki uporabniškega vmesnika

\n' + '\n' + '

Če se želite pomakniti z enega dela uporabniškega vmesnika na naslednjega, pritisnite tabulatorko.

\n' + '\n' + '

Če se želite pomakniti z enega dela uporabniškega vmesnika na prejšnjega, pritisnite shift + tabulatorko.

\n' + '\n' + '

Zaporedje teh razdelkov uporabniškega vmesnika, ko pritiskate tabulatorko, je:

\n' + '\n' + '
    \n' + '
  1. Menijska vrstica
  2. \n' + '
  3. Posamezne skupine orodne vrstice
  4. \n' + '
  5. Stranska vrstica
  6. \n' + '
  7. Pod do elementa v nogi
  8. \n' + '
  9. Gumb za preklop štetja besed v nogi
  10. \n' + '
  11. Povezava do blagovne znamke v nogi
  12. \n' + '
  13. Ročaj za spreminjanje velikosti urejevalnika v nogi
  14. \n' + '
\n' + '\n' + '

Če razdelek uporabniškega vmesnika ni prisoten, je preskočen.

\n' + '\n' + '

Če ima noga fokus za krmarjenje s tipkovnico in ni vidne stranske vrstice, s pritiskom na shift + tabulatorko\n' + ' fokus premaknete na prvo skupino orodne vrstice, ne zadnjo.

\n' + '\n' + '

Krmarjenje v razdelkih uporabniškega vmesnika

\n' + '\n' + '

Če se želite premakniti z enega elementa uporabniškega vmesnika na naslednjega, pritisnite ustrezno puščično tipko.

\n' + '\n' + '

Leva in desna puščična tipka

\n' + '\n' + '\n' + '\n' + '

Spodnja in zgornja puščična tipka

\n' + '\n' + '\n' + '\n' + '

Puščične tipke omogočajo kroženje znotraj razdelka uporabniškega vmesnika, na katerem je fokus.

\n' + '\n' + '

Če želite zapreti odprt meni, podmeni ali pojavni meni, pritisnite tipko Esc.

\n' + '\n' + '

Če je trenutni fokus na »vrhu« določenega razdelka uporabniškega vmesnika, s pritiskom tipke Esc zaprete\n' + ' tudi celotno krmarjenje s tipkovnico.

\n' + '\n' + '

Izvajanje menijskega elementa ali gumba orodne vrstice

\n' + '\n' + '

Ko je označen želeni menijski element ali orodja vrstica, pritisnite vračalko, Enter\n' + ' ali preslednico, da izvedete element.

\n' + '\n' + '

Krmarjenje po pogovornih oknih brez zavihkov

\n' + '\n' + '

Ko odprete pogovorno okno brez zavihkov, ima fokus prva interaktivna komponenta.

\n' + '\n' + '

Med interaktivnimi komponentami pogovornega okna se premikate s pritiskom tabulatorke ali kombinacije tipke shift + tabulatorke.

\n' + '\n' + '

Krmarjenje po pogovornih oknih z zavihki

\n' + '\n' + '

Ko odprete pogovorno okno z zavihki, ima fokus prvi gumb v meniju zavihka.

\n' + '\n' + '

Med interaktivnimi komponentami tega zavihka pogovornega okna se premikate s pritiskom tabulatorke ali\n' + ' kombinacije tipke shift + tabulatorke.

\n' + '\n' + '

Na drug zavihek pogovornega okna preklopite tako, da fokus prestavite na meni zavihka in nato pritisnete ustrezno puščično\n' + ' tipko, da se pomaknete med razpoložljivimi zavihki.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/sv_SE.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.sv_SE', '

Påbörja tangentbordsnavigering

\n' + '\n' + '
\n' + '
Fokusera på menyraden
\n' + '
Windows eller Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Fokusera på verktygsraden
\n' + '
Windows eller Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Fokusera på verktygsraden
\n' + '
Windows eller Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Fokusera aviseringen
\n' + '
Windows eller Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Fokusera på en snabbverktygsrad
\n' + '
Windows, Linux eller macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Navigeringen börjar vid det första gränssnittsobjektet, vilket är markerat eller understruket om det gäller det första objektet i\n' + ' sidfotens elementsökväg.

\n' + '\n' + '

Navigera mellan UI-avsnitt

\n' + '\n' + '

Flytta från ett UI-avsnitt till nästa genom att trycka på Tabb.

\n' + '\n' + '

Flytta från ett UI-avsnitt till det föregående genom att trycka på Skift+Tabb.

\n' + '\n' + '

Tabb-ordningen för dessa UI-avsnitt är:

\n' + '\n' + '
    \n' + '
  1. Menyrad
  2. \n' + '
  3. Varje verktygsradsgrupp
  4. \n' + '
  5. Sidoruta
  6. \n' + '
  7. Elementsökväg i sidfoten
  8. \n' + '
  9. Växlingsknapp för ordantal i sidfoten
  10. \n' + '
  11. Varumärkeslänk i sidfoten
  12. \n' + '
  13. Storlekshandtag för redigeraren i sidfoten
  14. \n' + '
\n' + '\n' + '

Om ett UI-avsnitt inte finns hoppas det över.

\n' + '\n' + '

Om sidfoten har fokus på tangentbordsnavigering, och det inte finns någon synlig sidoruta, flyttas fokus till den första verktygsradsgruppen\n' + ' när du trycker på Skift+Tabb, inte till den sista.

\n' + '\n' + '

Navigera i UI-avsnitt

\n' + '\n' + '

Flytta från ett UI-element till nästa genom att trycka på motsvarande piltangent.

\n' + '\n' + '

Vänsterpil och högerpil

\n' + '\n' + '\n' + '\n' + '

Nedpil och uppil

\n' + '\n' + '\n' + '\n' + '

Piltangenterna cirkulerar inom det fokuserade UI-avsnittet.

\n' + '\n' + '

Tryck på Esc-tangenten om du vill stänga en öppen meny, undermeny eller popup-meny.

\n' + '\n' + '

Om det aktuella fokuset är högst upp i ett UI-avsnitt avlutas även tangentbordsnavigeringen helt när\n' + ' du trycker på Esc-tangenten.

\n' + '\n' + '

Köra ett menyalternativ eller en verktygfältsknapp

\n' + '\n' + '

När menyalternativet eller verktygsradsknappen är markerad trycker du på Retur, Enter\n' + ' eller blanksteg för att köra alternativet.

\n' + '\n' + '

Navigera i dialogrutor utan flikar

\n' + '\n' + '

I dialogrutor utan flikar är den första interaktiva komponenten i fokus när dialogrutan öppnas.

\n' + '\n' + '

Navigera mellan interaktiva dialogkomponenter genom att trycka på Tabb eller Skift+Tabb.

\n' + '\n' + '

Navigera i dialogrutor med flikar

\n' + '\n' + '

I dialogrutor utan flikar är den första knappen på flikmenyn i fokus när dialogrutan öppnas.

\n' + '\n' + '

Navigera mellan interaktiva komponenter på dialogrutefliken genom att trycka på Tabb eller\n' + ' Skift+Tabb.

\n' + '\n' + '

Växla till en annan dialogruta genom att fokusera på flikmenyn och sedan trycka på motsvarande piltangent\n' + ' för att cirkulera mellan de tillgängliga flikarna.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/th_TH.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.th_TH', '

เริ่มต้นการนำทางด้วยแป้นพิมพ์

\n' + '\n' + '
\n' + '
โฟกัสที่แถบเมนู
\n' + '
Windows หรือ Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
โฟกัสที่แถบเครื่องมือ
\n' + '
Windows หรือ Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
โฟกัสที่ส่วนท้าย
\n' + '
Windows หรือ Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
โฟกัสไปที่การแจ้งเตือน
\n' + '
Windows หรือ Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
โฟกัสที่แถบเครื่องมือตามบริบท
\n' + '
Windows, Linux หรือ macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

การนำทางจะเริ่มที่รายการ UI แรก ซึ่งจะมีการไฮไลต์หรือขีดเส้นใต้ไว้ในกรณีที่รายการแรกอยู่ใน\n' + ' พาธองค์ประกอบส่วนท้าย

\n' + '\n' + '

การนำทางระหว่างส่วนต่างๆ ของ UI

\n' + '\n' + '

ในการย้ายจากส่วน UI หนึ่งไปยังส่วนถัดไป ให้กด Tab

\n' + '\n' + '

ในการย้ายจากส่วน UI หนึ่งไปยังส่วนก่อนหน้า ให้กด Shift+Tab

\n' + '\n' + '

ลำดับแท็บของส่วนต่างๆ ของ UI คือ:

\n' + '\n' + '
    \n' + '
  1. แถบเมนู
  2. \n' + '
  3. แต่ละกลุ่มแถบเครื่องมือ
  4. \n' + '
  5. แถบข้าง
  6. \n' + '
  7. พาธองค์ประกอบในส่วนท้าย
  8. \n' + '
  9. ปุ่มสลับเปิด/ปิดจำนวนคำในส่วนท้าย
  10. \n' + '
  11. ลิงก์ชื่อแบรนด์ในส่วนท้าย
  12. \n' + '
  13. จุดจับปรับขนาดของตัวแก้ไขในส่วนท้าย
  14. \n' + '
\n' + '\n' + '

หากส่วน UI ไม่ปรากฏ แสดงว่าถูกข้ามไป

\n' + '\n' + '

หากส่วนท้ายมีการโฟกัสการนำทางแป้นพิมพ์และไม่มีแถบข้างปรากฏ การกด Shift+Tab\n' + ' จะย้ายการโฟกัสไปที่กลุ่มแถบเครื่องมือแรก ไม่ใช่สุดท้าย

\n' + '\n' + '

การนำทางภายในส่วนต่างๆ ของ UI

\n' + '\n' + '

ในการย้ายจากองค์ประกอบ UI หนึ่งไปยังองค์ประกอบส่วนถัดไป ให้กดปุ่มลูกศรที่เหมาะสม

\n' + '\n' + '

ปุ่มลูกศรซ้ายและขวา

\n' + '\n' + '\n' + '\n' + '

ปุ่มลูกศรลงและขึ้น

\n' + '\n' + '\n' + '\n' + '

ปุ่มลูกศรจะเลื่อนไปมาภายในส่วน UI ที่โฟกัส

\n' + '\n' + '

ในการปิดเมนูที่เปิดอยู่ เมนูย่อยที่เปิดอยู่ หรือเมนูป๊อบอัพที่เปิดอยู่ ให้กดปุ่ม Esc

\n' + '\n' + '

หากโฟกัสปัจจุบันอยู่ที่ ‘ด้านบนสุด’ ของส่วน UI เฉพาะ การกดปุ่ม Esc จะทำให้ออกจาก\n' + ' การนำทางด้วยแป้นพิมพ์ทั้งหมดเช่นกัน

\n' + '\n' + '

การดำเนินการรายการเมนูหรือปุ่มในแถบเครื่องมือ

\n' + '\n' + '

เมื่อไฮไลต์รายการเมนูหรือปุ่มในแถบเครื่องมือที่ต้องการ ให้กด Return, Enter\n' + ' หรือ Space bar เพื่อดำเนินการรายการดังกล่าว

\n' + '\n' + '

การนำทางสำหรับกล่องโต้ตอบที่ไม่อยู่ในแท็บ

\n' + '\n' + '

ในกล่องโต้ตอบที่ไม่อยู่ในแท็บ จะโฟกัสที่ส่วนประกอบเชิงโต้ตอบแรกเมื่อกล่องโต้ตอบเปิด

\n' + '\n' + '

นำทางระหว่างส่วนประกอบเชิงโต้ตอบต่างๆ ของกล่องโต้ตอบ โดยการกด Tab หรือ Shift+Tab

\n' + '\n' + '

การนำทางสำหรับกล่องโต้ตอบที่อยู่ในแท็บ

\n' + '\n' + '

ในกล่องโต้ตอบที่อยู่ในแท็บ จะโฟกัสที่ปุ่มแรกในเมนูแท็บเมื่อกล่องโต้ตอบเปิด

\n' + '\n' + '

นำทางระหว่างส่วนประกอบเชิงโต้ตอบต่างๆ ของแท็บกล่องโต้ตอบนี้โดยการกด Tab หรือ\n' + ' Shift+Tab

\n' + '\n' + '

สลับไปยังแท็บกล่องโต้ตอบอื่นโดยการเลือกโฟกัสที่เมนูแท็บ แล้วกดปุ่มลูกศรที่เหมาะสม\n' + ' เพื่อเลือกแท็บที่ใช้ได้

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/tr.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.tr', '

Klavyeyle gezintiyi başlatma

\n' + '\n' + '
\n' + '
Menü çubuğuna odaklan
\n' + '
Windows veya Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Araç çubuğuna odaklan
\n' + '
Windows veya Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Alt bilgiye odaklan
\n' + '
Windows veya Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Bildirime odakla
\n' + '
Windows veya Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Bağlamsal araç çubuğuna odaklan
\n' + '
Windows, Linux veya macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Gezinti ilk kullanıcı arabirimi öğesinden başlar, bu öğe vurgulanır ya da ilk öğe, Alt bilgi elemanı\n' + ' yolundaysa altı çizilir.

\n' + '\n' + '

Kullanıcı arabirimi bölümleri arasında gezinme

\n' + '\n' + '

Sonraki kullanıcı arabirimi bölümüne gitmek için Sekme tuşuna basın.

\n' + '\n' + '

Önceki kullanıcı arabirimi bölümüne gitmek için Shift+Sekme tuşlarına basın.

\n' + '\n' + '

Bu kullanıcı arabirimi bölümlerinin Sekme sırası:

\n' + '\n' + '
    \n' + '
  1. Menü çubuğu
  2. \n' + '
  3. Her araç çubuğu grubu
  4. \n' + '
  5. Kenar çubuğu
  6. \n' + '
  7. Alt bilgide öğe yolu
  8. \n' + '
  9. Alt bilgide sözcük sayısı geçiş düğmesi
  10. \n' + '
  11. Alt bilgide marka bağlantısı
  12. \n' + '
  13. Alt bilgide düzenleyiciyi yeniden boyutlandırma tutamacı
  14. \n' + '
\n' + '\n' + '

Kullanıcı arabirimi bölümü yoksa atlanır.

\n' + '\n' + '

Alt bilgide klavyeyle gezinti odağı yoksa ve görünür bir kenar çubuğu mevcut değilse Shift+Sekme tuşlarına basıldığında\n' + ' odak son araç çubuğu yerine ilk araç çubuğu grubuna taşınır.

\n' + '\n' + '

Kullanıcı arabirimi bölümleri içinde gezinme

\n' + '\n' + '

Sonraki kullanıcı arabirimi elemanına gitmek için uygun Ok tuşuna basın.

\n' + '\n' + '

Sol ve Sağ ok tuşları

\n' + '\n' + '\n' + '\n' + '

Aşağı ve Yukarı ok tuşları

\n' + '\n' + '\n' + '\n' + '

Ok tuşları, odaklanılan kullanıcı arabirimi bölümü içinde döngüsel olarak hareket eder.

\n' + '\n' + '

Açık bir menüyü, açık bir alt menüyü veya açık bir açılır menüyü kapatmak için Esc tuşuna basın.

\n' + '\n' + '

Geçerli odak belirli bir kullanıcı arabirimi bölümünün "üst" kısmındaysa Esc tuşuna basıldığında\n' + ' klavyeyle gezintiden de tamamen çıkılır.

\n' + '\n' + '

Menü öğesini veya araç çubuğu düğmesini yürütme

\n' + '\n' + '

İstediğiniz menü öğesi veya araç çubuğu düğmesi vurgulandığında Return, Enter\n' + ' veya Ara çubuğu tuşuna basın.

\n' + '\n' + '

Sekme bulunmayan iletişim kutularında gezinme

\n' + '\n' + '

Sekme bulunmayan iletişim kutularında, iletişim kutusu açıldığında ilk etkileşimli bileşene odaklanılır.

\n' + '\n' + '

Etkileşimli iletişim kutusu bileşenleri arasında gezinmek için Sekme veya Shift+ Sekme tuşlarına basın.

\n' + '\n' + '

Sekmeli iletişim kutularında gezinme

\n' + '\n' + '

Sekmeli iletişim kutularında, iletişim kutusu açıldığında sekme menüsündeki ilk düğmeye odaklanılır.

\n' + '\n' + '

Bu iletişim kutusu sekmesinin etkileşimli bileşenleri arasında gezinmek için Sekme veya\n' + ' Shift+Sekme tuşlarına basın.

\n' + '\n' + '

Mevcut sekmeler arasında geçiş yapmak için sekme menüsüne odaklanıp uygun Ok tuşuna basarak\n' + ' başka bir iletişim kutusu sekmesine geçiş yapın.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/uk.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.uk', '

Початок роботи з навігацією за допомогою клавіатури

\n' + '\n' + '
\n' + '
Фокус на рядок меню
\n' + '
Windows або Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Фокус на панелі інструментів
\n' + '
Windows або Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Фокус на розділі "Нижній колонтитул"
\n' + '
Windows або Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Фокус на сповіщення
\n' + '
Windows або Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Фокус на контекстній панелі інструментів
\n' + '
Windows, Linux або macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Навігація почнеться з першого елемента інтерфейсу користувача, який буде виділено або підкреслено в разі, якщо перший елемент знаходиться в\n' + ' шляху до елемента "Нижній колонтитул".

\n' + '\n' + '

Навігація між розділами інтерфейсу користувача

\n' + '\n' + '

Щоб перейти з одного розділу інтерфейсу користувача до наступного розділу, натисніть клавішу Tab.

\n' + '\n' + '

Щоб перейти з одного розділу інтерфейсу користувача до попереднього розділу, натисніть сполучення клавіш Shift+Tab.

\n' + '\n' + '

Порядок Вкладок цих розділів інтерфейсу користувача такий:

\n' + '\n' + '
    \n' + '
  1. Рядок меню
  2. \n' + '
  3. Кожна група панелей інструментів
  4. \n' + '
  5. Бічна панель
  6. \n' + '
  7. Шлях до елементів у розділі "Нижній колонтитул"
  8. \n' + '
  9. Кнопка перемикача "Кількість слів" у розділі "Нижній колонтитул"
  10. \n' + '
  11. Посилання на брендинг у розділі "Нижній колонтитул"
  12. \n' + '
  13. Маркер змінення розміру в розділі "Нижній колонтитул"
  14. \n' + '
\n' + '\n' + '

Якщо розділ інтерфейсу користувача відсутній, він пропускається.

\n' + '\n' + '

Якщо фокус навігації клавіатури знаходиться на розділі "Нижній колонтитул", але користувач не бачить видиму бічну панель, натисніть Shift+Tab,\n' + ' щоб перемістити фокус на першу групу панелі інструментів, а не на останню.

\n' + '\n' + '

Навігація в межах розділів інтерфейсу користувача

\n' + '\n' + '

Щоб перейти з одного елементу інтерфейсу користувача до наступного, натисніть відповідну клавішу зі стрілкою.

\n' + '\n' + '

Клавіші зі стрілками Ліворуч і Праворуч

\n' + '\n' + '\n' + '\n' + '

Клавіші зі стрілками Вниз і Вгору

\n' + '\n' + '\n' + '\n' + '

Клавіші зі стрілками переміщують фокус циклічно в межах розділу інтерфейсу користувача, на якому знаходиться фокус.

\n' + '\n' + '

Щоб закрити відкрите меню, відкрите вкладене меню або відкрите спливаюче меню, натисніть клавішу Esc.

\n' + '\n' + '

Якщо поточний фокус знаходиться на верхньому рівні певного розділу інтерфейсу користувача, натискання клавіші Esc також виконує вихід\n' + ' з навігації за допомогою клавіатури повністю.

\n' + '\n' + '

Виконання елементу меню або кнопки панелі інструментів

\n' + '\n' + '

Коли потрібний елемент меню або кнопку панелі інструментів виділено, натисніть клавіші Return, Enter,\n' + ' або Пробіл, щоб виконати цей елемент.

\n' + '\n' + '

Навігація по діалоговим вікнам без вкладок

\n' + '\n' + '

У діалогових вікнах без вкладок перший інтерактивний компонент приймає фокус, коли відкривається діалогове вікно.

\n' + '\n' + '

Переходьте між інтерактивними компонентами діалогового вікна, натискаючи клавіші Tab або Shift+Tab.

\n' + '\n' + '

Навігація по діалоговим вікнам з вкладками

\n' + '\n' + '

У діалогових вікнах із вкладками перша кнопка в меню вкладки приймає фокус, коли відкривається діалогове вікно.

\n' + '\n' + '

Переходьте між інтерактивними компонентами цієї вкладки діалогового вікна, натискаючи клавіші Tab або\n' + ' Shift+Tab.

\n' + '\n' + '

Щоб перейти на іншу вкладку діалогового вікна, перемістіть фокус на меню вкладки, а потім натисніть відповідну клавішу зі стрілкою,\n' + ' щоб циклічно переходити по доступним вкладкам.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/vi.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.vi', '

Bắt đầu điều hướng bàn phím

\n' + '\n' + '
\n' + '
Tập trung vào thanh menu
\n' + '
Windows hoặc Linux: Alt+F9
\n' + '
macOS: ⌥F9
\n' + '
Tập trung vào thanh công cụ
\n' + '
Windows hoặc Linux: Alt+F10
\n' + '
macOS: ⌥F10
\n' + '
Tập trung vào chân trang
\n' + '
Windows hoặc Linux: Alt+F11
\n' + '
macOS: ⌥F11
\n' + '
Tập trung vào thông báo
\n' + '
Windows hoặc Linux: Alt+F12
\n' + '
macOS: ⌥F12
\n' + '
Tập trung vào thanh công cụ ngữ cảnh
\n' + '
Windows, Linux hoặc macOS: Ctrl+F9
\n' + '
\n' + '\n' + '

Điều hướng sẽ bắt đầu từ mục UI đầu tiên. Mục này sẽ được tô sáng hoặc có gạch dưới (nếu là mục đầu tiên trong\n' + ' đường dẫn phần tử Chân trang).

\n' + '\n' + '

Di chuyển qua lại giữa các phần UI

\n' + '\n' + '

Để di chuyển từ một phần UI sang phần tiếp theo, ấn Tab.

\n' + '\n' + '

Để di chuyển từ một phần UI về phần trước đó, ấn Shift+Tab.

\n' + '\n' + '

Thứ tự Tab của các phần UI này như sau:

\n' + '\n' + '
    \n' + '
  1. Thanh menu
  2. \n' + '
  3. Từng nhóm thanh công cụ
  4. \n' + '
  5. Thanh bên
  6. \n' + '
  7. Đường dẫn phần tử trong chân trang
  8. \n' + '
  9. Nút chuyển đổi đếm chữ ở chân trang
  10. \n' + '
  11. Liên kết thương hiệu ở chân trang
  12. \n' + '
  13. Núm điều tác chỉnh kích cỡ trình soạn thảo ở chân trang
  14. \n' + '
\n' + '\n' + '

Nếu người dùng không thấy một phần UI, thì có nghĩa phần đó bị bỏ qua.

\n' + '\n' + '

Nếu ở chân trang có tính năng tập trung điều hướng bàn phím, mà không có thanh bên nào hiện hữu, thao tác ấn Shift+Tab\n' + ' sẽ chuyển hướng tập trung vào nhóm thanh công cụ đầu tiên, không phải cuối cùng.

\n' + '\n' + '

Di chuyển qua lại trong các phần UI

\n' + '\n' + '

Để di chuyển từ một phần tử UI sang phần tiếp theo, ấn phím Mũi tên tương ứng cho phù hợp.

\n' + '\n' + '

Các phím mũi tên TráiPhải

\n' + '\n' + '\n' + '\n' + '

Các phím mũi tên Hướng xuốngHướng lên

\n' + '\n' + '\n' + '\n' + '

Các phím mũi tên xoay vòng trong một phần UI tập trung.

\n' + '\n' + '

Để đóng một menu mở, một menu phụ đang mở, hoặc một menu dạng bật lên đang mở, hãy ấn phím Esc.

\n' + '\n' + '

Nếu trọng tâm hiện tại là ở phần “đầu” của một phần UI cụ thể, thao tác ấn phím Esc cũng sẽ thoát\n' + ' toàn bộ phần điều hướng bàn phím.

\n' + '\n' + '

Thực hiện chức năng của một mục menu hoặc nút thanh công cụ

\n' + '\n' + '

Khi mục menu hoặc nút thanh công cụ muốn dùng được tô sáng, hãy ấn Return, Enter,\n' + ' hoặc Phím cách để thực hiện chức năng mục đó.

\n' + '\n' + '

Điều hướng giữa các hộp thoại không có nhiều tab

\n' + '\n' + '

Trong các hộp thoại không có nhiều tab, khi hộp thoại mở ra, trọng tâm sẽ hướng vào thành phần tương tác đầu tiên.

\n' + '\n' + '

Di chuyển giữa các thành phần hộp thoại tương tác bằng cách ấn Tab hoặc Shift+Tab.

\n' + '\n' + '

Điều hướng giữa các hộp thoại có nhiều tab

\n' + '\n' + '

Trong các hộp thoại có nhiều tab, khi hộp thoại mở ra, trọng tâm sẽ hướng vào nút đầu tiên trong menu tab.

\n' + '\n' + '

Di chuyển giữa các thành phần tương tác của tab hộp thoại này bằng cách ấn Tab hoặc\n' + ' Shift+Tab.

\n' + '\n' + '

Chuyển sang một tab hộp thoại khác bằng cách chuyển trọng tâm vào menu tab, rồi ấn phím Mũi tên phù hợp\n' + ' để xoay vòng các tab hiện có.

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_CN.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_CN', '

开始键盘导航

\n' + '\n' + '
\n' + '
使菜单栏处于焦点
\n' + '
Windows 或 Linux:Alt+F9
\n' + '
macOS:⌥F9
\n' + '
使工具栏处于焦点
\n' + '
Windows 或 Linux:Alt+F10
\n' + '
macOS:⌥F10
\n' + '
使页脚处于焦点
\n' + '
Windows 或 Linux:Alt+F11
\n' + '
macOS:⌥F11
\n' + '
使通知处于焦点
\n' + '
Windows 或 Linux:Alt+F12
\n' + '
macOS:⌥F12
\n' + '
使上下文工具栏处于焦点
\n' + '
Windows、Linux 或 macOS:Ctrl+F9
\n' + '
\n' + '\n' + '

导航将在第一个 UI 项上开始,其中突出显示该项,或者对于页脚元素路径中的第一项,将为其添加下划线。

\n' + '\n' + '

在 UI 部分之间导航

\n' + '\n' + '

要从一个 UI 部分移至下一个,请按 Tab

\n' + '\n' + '

要从一个 UI 部分移至上一个,请按 Shift+Tab

\n' + '\n' + '

这些 UI 部分的 Tab 顺序为:

\n' + '\n' + '
    \n' + '
  1. 菜单栏
  2. \n' + '
  3. 每个工具栏组
  4. \n' + '
  5. 边栏
  6. \n' + '
  7. 页脚中的元素路径
  8. \n' + '
  9. 页脚中的字数切换按钮
  10. \n' + '
  11. 页脚中的品牌链接
  12. \n' + '
  13. 页脚中的编辑器调整大小图柄
  14. \n' + '
\n' + '\n' + '

如果不存在某个 UI 部分,则跳过它。

\n' + '\n' + '

如果键盘导航焦点在页脚,并且没有可见的边栏,则按 Shift+Tab 将焦点移至第一个工具栏组而非最后一个。

\n' + '\n' + '

在 UI 部分内导航

\n' + '\n' + '

要从一个 UI 元素移至下一个,请按相应的箭头键。

\n' + '\n' + '

箭头键

\n' + '\n' + '\n' + '\n' + '

箭头键

\n' + '\n' + '\n' + '\n' + '

箭头键在具有焦点的 UI 部分内循环。

\n' + '\n' + '

要关闭打开的菜单、打开的子菜单或打开的弹出菜单,请按 Esc 键。

\n' + '\n' + '

如果当前的焦点在特定 UI 部分的“顶部”,则按 Esc 键还将完全退出键盘导航。

\n' + '\n' + '

执行菜单项或工具栏按钮

\n' + '\n' + '

当突出显示所需的菜单项或工具栏按钮时,按 ReturnEnter空格以执行该项。

\n' + '\n' + '

在非标签页式对话框中导航

\n' + '\n' + '

在非标签页式对话框中,当对话框打开时,第一个交互组件获得焦点。

\n' + '\n' + '

通过按 TabShift+Tab,在交互对话框组件之间导航。

\n' + '\n' + '

在标签页式对话框中导航

\n' + '\n' + '

在标签页式对话框中,当对话框打开时,标签页菜单中的第一个按钮获得焦点。

\n' + '\n' + '

通过按 TabShift+Tab,在此对话框的交互组件之间导航。

\n' + '\n' + '

通过将焦点移至另一对话框标签页的菜单,然后按相应的箭头键以在可用的标签页间循环,从而切换到该对话框标签页。

\n'); ================================================ FILE: apps/web-antd/public/tinymce/plugins/help/js/i18n/keynav/zh_TW.js ================================================ tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_TW', '

開始鍵盤瀏覽

\n' + '\n' + '
\n' + '
跳至功能表列
\n' + '
Windows 或 Linux:Alt+F9
\n' + '
macOS:⌥F9
\n' + '
跳至工具列
\n' + '
Windows 或 Linux:Alt+F10
\n' + '
macOS:⌥F10
\n' + '
跳至頁尾
\n' + '
Windows 或 Linux:Alt+F11
\n' + '
macOS:⌥F11
\n' + '
跳至通知
\n' + '
Windows 或 Linux:Alt+F12
\n' + '
macOS:⌥F12
\n' + '
跳至關聯式工具列
\n' + '
Windows、Linux 或 macOS:Ctrl+F9
\n' + '
\n' + '\n' + '

瀏覽會從第一個 UI 項目開始,該項目會反白顯示,但如果是「頁尾」元素路徑的第一項,\n' + ' 則加底線。

\n' + '\n' + '

在 UI 區段之間瀏覽

\n' + '\n' + '

從 UI 區段移至下一個,請按 Tab

\n' + '\n' + '

從 UI 區段移回上一個,請按 Shift+Tab

\n' + '\n' + '

這些 UI 區段的 Tab 順序如下:

\n' + '\n' + '
    \n' + '
  1. 功能表列
  2. \n' + '
  3. 各個工具列群組
  4. \n' + '
  5. 側邊欄
  6. \n' + '
  7. 頁尾中的元素路徑
  8. \n' + '
  9. 頁尾中字數切換按鈕
  10. \n' + '
  11. 頁尾中的品牌連結
  12. \n' + '
  13. 頁尾中編輯器調整大小控點
  14. \n' + '
\n' + '\n' + '

如果 UI 區段未顯示,表示已略過該區段。

\n' + '\n' + '

如果鍵盤瀏覽跳至頁尾,但沒有顯示側邊欄,則按下 Shift+Tab\n' + ' 會跳至第一個工具列群組,而不是最後一個。

\n' + '\n' + '

在 UI 區段之內瀏覽

\n' + '\n' + '

在兩個 UI 元素之間移動,請按適當的方向鍵。

\n' + '\n' + '

向左向右方向鍵

\n' + '\n' + '\n' + '\n' + '

向下向上方向鍵

\n' + '\n' + '\n' + '\n' + '

方向鍵會在所跳至 UI 區段之內循環。

\n' + '\n' + '

若要關閉已開啟的功能表、已開啟的子功能表,或已開啟的快顯功能表,請按 Esc 鍵。

\n' + '\n' + '

如果目前已跳至特定 UI 區段的「頂端」,則按 Esc 鍵也會結束\n' + ' 整個鍵盤瀏覽。

\n' + '\n' + '

執行功能表列項目或工具列按鈕

\n' + '\n' + '

當想要的功能表項目或工具列按鈕已反白顯示時,按 ReturnEnter、\n' + ' 或空白鍵即可執行該項目。

\n' + '\n' + '

瀏覽非索引標籤式對話方塊

\n' + '\n' + '

在非索引標籤式對話方塊中,開啟對話方塊時會跳至第一個互動元件。

\n' + '\n' + '

TabShift+Tab 即可在互動式對話方塊元件之間瀏覽。

\n' + '\n' + '

瀏覽索引標籤式對話方塊

\n' + '\n' + '

在索引標籤式對話方塊中,開啟對話方塊時會跳至索引標籤式功能表中的第一個按鈕。

\n' + '\n' + '

若要在此對話方塊的互動式元件之間瀏覽,請按 Tab 或\n' + ' Shift+Tab

\n' + '\n' + '

先跳至索引標籤式功能表,然後按適當的方向鍵,即可切換至另一個對話方塊索引標籤,\n' + ' 以循環瀏覽可用的索引標籤。

\n'); ================================================ FILE: apps/web-antd/public/tinymce/skins/content/dark/content.js ================================================ tinymce.Resource.add('content/dark/content.css', "body{background-color:#222f3e;color:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/content/default/content.js ================================================ tinymce.Resource.add('content/default/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/content/document/content.js ================================================ tinymce.Resource.add('content/document/content.css', "@media screen{html{background:#f4f4f4;min-height:100%}}body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif}@media screen{body{background-color:#fff;box-shadow:0 0 4px rgba(0,0,0,.15);box-sizing:border-box;margin:1rem auto 0;max-width:820px;min-height:calc(100vh - 1rem);padding:4rem 6rem 6rem 6rem}}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure figcaption{color:#999;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/content/tinymce-5/content.js ================================================ tinymce.Resource.add('content/tinymce-5/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/content/tinymce-5-dark/content.js ================================================ tinymce.Resource.add('content/tinymce-5-dark/content.css', "body{background-color:#2f3742;color:#dfe0e4;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem}a{color:#4099ff}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#6d737b}figure{display:table;margin:1rem auto}figure figcaption{color:#8a8f97;display:block;margin-top:.25rem;text-align:center}hr{border-color:#6d737b;border-style:solid;border-width:1px 0 0 0}code{background-color:#6d737b;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #6d737b;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #6d737b;margin-right:1.5rem;padding-right:1rem}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/content/writer/content.js ================================================ tinymce.Resource.add('content/writer/content.css', "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,'Open Sans','Helvetica Neue',sans-serif;line-height:1.4;margin:1rem auto;max-width:900px}table{border-collapse:collapse}table:not([cellpadding]) td,table:not([cellpadding]) th{padding:.4rem}table[border]:not([border=\"0\"]):not([style*=border-width]) td,table[border]:not([border=\"0\"]):not([style*=border-width]) th{border-width:1px}table[border]:not([border=\"0\"]):not([style*=border-style]) td,table[border]:not([border=\"0\"]):not([style*=border-style]) th{border-style:solid}table[border]:not([border=\"0\"]):not([style*=border-color]) td,table[border]:not([border=\"0\"]):not([style*=border-color]) th{border-color:#ccc}figure{display:table;margin:1rem auto}figure figcaption{color:#999;display:block;margin-top:.25rem;text-align:center}hr{border-color:#ccc;border-style:solid;border-width:1px 0 0 0}code{background-color:#e8e8e8;border-radius:3px;padding:.1rem .2rem}.mce-content-body:not([dir=rtl]) blockquote{border-left:2px solid #ccc;margin-left:1.5rem;padding-left:1rem}.mce-content-body[dir=rtl] blockquote{border-right:2px solid #ccc;margin-right:1.5rem;padding-right:1rem}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide/content.inline.js ================================================ tinymce.Resource.add('ui/default/content.inline.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.1)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.1);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}") //# sourceMappingURL=content.inline.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide/content.js ================================================ tinymce.Resource.add('ui/default/content.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.1)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.1);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}body{font-family:sans-serif}table{border-collapse:collapse}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide/skin.js ================================================ tinymce.Resource.add('ui/default/skin.css', ".tox{box-shadow:none;box-sizing:content-box;color:#222f3e;cursor:auto;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-style:normal;font-weight:400;line-height:normal;-webkit-tap-highlight-color:transparent;text-decoration:none;text-shadow:none;text-transform:none;vertical-align:initial;white-space:normal}.tox :not(svg):not(rect){box-sizing:inherit;color:inherit;cursor:inherit;direction:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:inherit;-webkit-tap-highlight-color:inherit;text-align:inherit;text-decoration:inherit;text-shadow:inherit;text-transform:inherit;vertical-align:inherit;white-space:inherit}.tox :not(svg):not(rect){background:0 0;border:0;box-shadow:none;float:none;height:auto;margin:0;max-width:none;outline:0;padding:0;position:static;width:auto}.tox:not([dir=rtl]){direction:ltr;text-align:left}.tox[dir=rtl]{direction:rtl;text-align:right}.tox-tinymce{border:2px solid #eee;border-radius:10px;box-shadow:none;box-sizing:border-box;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;overflow:hidden;position:relative;visibility:inherit!important}.tox.tox-tinymce-inline{border:none;box-shadow:none;overflow:initial}.tox.tox-tinymce-inline .tox-editor-container{overflow:initial}.tox.tox-tinymce-inline .tox-editor-header{background-color:#fff;border:2px solid #eee;border-radius:10px;box-shadow:none;overflow:hidden}.tox-tinymce-aux{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;z-index:1300}.tox-tinymce :focus,.tox-tinymce-aux :focus{outline:0}button::-moz-focus-inner{border:0}.tox[dir=rtl] .tox-icon--flip svg{transform:rotateY(180deg)}.tox .accessibility-issue__header{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description{align-items:stretch;border-radius:6px;display:flex;justify-content:space-between}.tox .accessibility-issue__description>div{padding-bottom:4px}.tox .accessibility-issue__description>div>div{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description>div>div .tox-icon svg{display:block}.tox .accessibility-issue__repair{margin-top:16px}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description{background-color:rgba(0,101,216,.1);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2{color:#006ce7}.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg{fill:#006ce7}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon{background-color:#006ce7;color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover{background-color:#0060ce}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active{background-color:#0054b4}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description{background-color:rgba(255,165,0,.08);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2{color:#8f5d00}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg{fill:#8f5d00}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon{background-color:#ffe89d;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover{background-color:#f2d574;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active{background-color:#e8c657;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description{background-color:rgba(204,0,0,.1);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2{color:#c00}.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg{fill:#c00}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon{background-color:#f2bfbf;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover{background-color:#e9a4a4;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active{background-color:#ee9494;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description{background-color:rgba(120,171,70,.1);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description>:last-child{display:none}.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2{color:#527530}.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg{fill:#527530}.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2{font-size:14px;margin-top:0}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-left:4px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-left:auto}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description{padding:4px 4px 4px 8px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-right:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-right:auto}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description{padding:4px 8px 4px 4px}.tox .mce-codemirror{background:#fff;bottom:0;font-size:13px;left:0;position:absolute;right:0;top:0;z-index:1}.tox .mce-codemirror.tox-inline-codemirror{margin:8px;position:absolute}.tox .tox-advtemplate .tox-form__grid{flex:1}.tox .tox-advtemplate .tox-form__grid>div:first-child{display:flex;flex-direction:column;width:30%}.tox .tox-advtemplate .tox-form__grid>div:first-child>div:nth-child(2){flex-basis:0;flex-grow:1;overflow:auto}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid>div:first-child{width:100%}}.tox .tox-advtemplate iframe{border-color:#eee;border-radius:10px;border-style:solid;border-width:1px;margin:0 10px}.tox .tox-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bottom-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bar{display:flex;flex:0 0 auto}.tox .tox-button{background-color:#006ce7;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#006ce7;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;line-height:24px;margin:0;outline:0;padding:4px 16px;position:relative;text-align:center;text-decoration:none;text-transform:none;white-space:nowrap}.tox .tox-button::before{border-radius:6px;bottom:-1px;box-shadow:inset 0 0 0 1px #fff,0 0 0 2px #006ce7;content:'';left:-1px;opacity:0;pointer-events:none;position:absolute;right:-1px;top:-1px}.tox .tox-button[disabled]{background-color:#006ce7;background-image:none;border-color:#006ce7;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button:focus:not(:disabled){background-color:#0060ce;background-image:none;border-color:#0060ce;box-shadow:none;color:#fff}.tox .tox-button:focus:not(:disabled)::before{opacity:1}.tox .tox-button:hover:not(:disabled){background-color:#0060ce;background-image:none;border-color:#0060ce;box-shadow:none;color:#fff}.tox .tox-button:active:not(:disabled){background-color:#0054b4;background-image:none;border-color:#0054b4;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled{background-color:#0054b4;background-image:none;border-color:#0054b4;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled[disabled]{background-color:#0054b4;background-image:none;border-color:#0054b4;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button.tox-button--enabled:focus:not(:disabled){background-color:#00489b;background-image:none;border-color:#00489b;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:hover:not(:disabled){background-color:#00489b;background-image:none;border-color:#00489b;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:active:not(:disabled){background-color:#003c81;background-image:none;border-color:#003c81;box-shadow:none;color:#fff}.tox .tox-button--icon-and-text,.tox .tox-button.tox-button--icon-and-text,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text{display:flex;padding:5px 4px}.tox .tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg{display:block;fill:currentColor}.tox .tox-button--secondary{background-color:#f0f0f0;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#f0f0f0;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;color:#222f3e;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;outline:0;padding:4px 16px;text-decoration:none;text-transform:none}.tox .tox-button--secondary[disabled]{background-color:#f0f0f0;background-image:none;border-color:#f0f0f0;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-button--secondary:focus:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--secondary:hover:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--secondary:active:not(:disabled){background-color:#d6d6d6;background-image:none;border-color:#d6d6d6;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled{background-color:#a8c8ed;background-image:none;border-color:#a8c8ed;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled[disabled]{background-color:#a8c8ed;background-image:none;border-color:#a8c8ed;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled){background-color:#93bbe9;background-image:none;border-color:#93bbe9;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled){background-color:#93bbe9;background-image:none;border-color:#93bbe9;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled){background-color:#7daee4;background-image:none;border-color:#7daee4;box-shadow:none;color:#222f3e}.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding:4px}.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display:block;fill:currentColor}.tox .tox-button-link{background:0;border:none;box-sizing:border-box;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;white-space:nowrap}.tox .tox-button-link--sm{font-size:14px}.tox .tox-button--naked{background-color:transparent;border-color:transparent;box-shadow:unset;color:#222f3e}.tox .tox-button--naked[disabled]{background-color:rgba(34,47,62,.12);border-color:transparent;box-shadow:unset;color:rgba(34,47,62,.5)}.tox .tox-button--naked:hover:not(:disabled){background-color:rgba(34,47,62,.12);border-color:transparent;box-shadow:unset;color:#222f3e}.tox .tox-button--naked:focus:not(:disabled){background-color:rgba(34,47,62,.12);border-color:transparent;box-shadow:unset;color:#222f3e}.tox .tox-button--naked:active:not(:disabled){background-color:rgba(34,47,62,.18);border-color:transparent;box-shadow:unset;color:#222f3e}.tox .tox-button--naked .tox-icon svg{fill:currentColor}.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color:#222f3e}.tox .tox-checkbox{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:36px;min-width:36px}.tox .tox-checkbox__input{height:1px;overflow:hidden;position:absolute;top:auto;width:1px}.tox .tox-checkbox__icons{align-items:center;border-radius:6px;box-shadow:0 0 0 2px transparent;box-sizing:content-box;display:flex;height:24px;justify-content:center;padding:calc(4px - 1px);width:24px}.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:block;fill:rgba(34,47,62,.3)}@media (forced-colors:active){.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:currentColor!important}}.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:none;fill:#006ce7}.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display:none;fill:#006ce7}.tox .tox-checkbox--disabled{color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg{fill:rgba(34,47,62,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:rgba(34,47,62,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:rgba(34,47,62,.5)}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display:block}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:block}.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:6px;box-shadow:inset 0 0 0 1px #006ce7;padding:calc(4px - 1px)}.tox:not([dir=rtl]) .tox-checkbox__label{margin-left:4px}.tox:not([dir=rtl]) .tox-checkbox__input{left:-10000px}.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left:4px}.tox[dir=rtl] .tox-checkbox__label{margin-right:4px}.tox[dir=rtl] .tox-checkbox__input{right:-10000px}.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right:4px}.tox .tox-collection--toolbar .tox-collection__group{display:flex;padding:0}.tox .tox-collection--grid .tox-collection__group{display:flex;flex-wrap:wrap;max-height:208px;overflow-x:hidden;overflow-y:auto;padding:0}.tox .tox-collection--list .tox-collection__group{border-bottom-width:0;border-color:#e3e3e3;border-left-width:0;border-right-width:0;border-style:solid;border-top-width:1px;padding:4px 0}.tox .tox-collection--list .tox-collection__group:first-child{border-top-width:0}.tox .tox-collection__group-heading{background-color:#fcfcfc;color:rgba(34,47,62,.7);cursor:default;font-size:12px;font-style:normal;font-weight:400;margin-bottom:4px;margin-top:-4px;padding:4px 8px;text-transform:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection__item{align-items:center;border-radius:3px;color:#222f3e;display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection--list .tox-collection__item{padding:4px 8px}.tox .tox-collection--toolbar .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--grid .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--list .tox-collection__item--enabled{background-color:#fff;color:#222f3e}.tox .tox-collection--list .tox-collection__item--active{background-color:#006ce7}.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{background-color:#a6ccf7;color:#222f3e}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active{background-color:#fff;position:relative}.tox .tox-collection--toolbar .tox-collection__item--active:hover{background-color:#f0f0f0;color:#222f3e}.tox .tox-collection--toolbar .tox-collection__item--active:focus{background-color:#f0f0f0;color:#222f3e}.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border:2px solid highlight}}.tox .tox-collection--grid .tox-collection__item--enabled{background-color:#a6ccf7;color:#222f3e}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:#f0f0f0;color:#222f3e;position:relative;z-index:1}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7 inset;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border:2px solid highlight}}.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#fff}@media (forced-colors:active){.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){border:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#222f3e}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled):hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection__item-checkmark,.tox .tox-collection__item-icon{align-items:center;display:flex;height:24px;justify-content:center;width:24px}.tox .tox-collection__item-checkmark svg,.tox .tox-collection__item-icon svg{fill:currentColor}.tox .tox-collection--toolbar-lg .tox-collection__item-icon{height:48px;width:48px}.tox .tox-collection__item-label{color:currentColor;display:inline-block;flex:1;font-size:14px;font-style:normal;font-weight:400;line-height:24px;max-width:100%;text-transform:none;word-break:break-all}.tox .tox-collection__item-accessory{color:currentColor;display:inline-block;font-size:14px;height:24px;line-height:24px;text-transform:none}.tox .tox-collection__item-caret{align-items:center;display:flex;min-height:24px}.tox .tox-collection__item-caret::after{content:'';font-size:0;min-height:inherit}.tox .tox-collection__item-caret svg{fill:currentColor}.tox .tox-collection__item--state-disabled{background-color:transparent;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg{fill:rgba(34,47,62,.5)}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display:none}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory+.tox-collection__item-checkmark{display:none}.tox .tox-collection--horizontal{background-color:#fff;border:1px solid #e3e3e3;border-radius:6px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:nowrap;margin-bottom:0;overflow-x:auto;padding:0}.tox .tox-collection--horizontal .tox-collection__group{align-items:center;display:flex;flex-wrap:nowrap;margin:0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item{height:28px;margin:6px 1px 5px 0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item-label{white-space:nowrap}.tox .tox-collection--horizontal .tox-collection__item-caret{margin-left:4px}.tox .tox-collection__item-container{display:flex}.tox .tox-collection__item-container--row{align-items:center;flex:1 1 auto;flex-direction:row}.tox .tox-collection__item-container--row.tox-collection__item-container--align-left{margin-right:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--align-right{justify-content:flex-end;margin-left:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top{align-items:flex-start;margin-bottom:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle{align-items:center}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom{align-items:flex-end;margin-top:auto}.tox .tox-collection__item-container--column{align-self:center;flex:1 1 auto;flex-direction:column}.tox .tox-collection__item-container--column.tox-collection__item-container--align-left{align-items:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--align-right{align-items:flex-end}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top{align-self:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle{align-self:center}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom{align-self:flex-end}.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-right:1px solid transparent}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left:8px}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-left:4px}.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left:16px;text-align:right}.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret{margin-left:16px}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-left:1px solid transparent}.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right:8px}.tox[dir=rtl] .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-right:4px}.tox[dir=rtl] .tox-collection__item-accessory{margin-right:16px;text-align:left}.tox[dir=rtl] .tox-collection .tox-collection__item-caret{margin-right:16px;transform:rotateY(180deg)}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret{margin-right:4px}@media (forced-colors:active){.tox .tox-hue-slider,.tox .tox-rgb-form .tox-rgba-preview{background-color:currentColor!important;border:1px solid highlight!important;forced-color-adjust:none}}.tox .tox-color-picker-container{display:flex;flex-direction:row;height:225px;margin:0}.tox .tox-sv-palette{box-sizing:border-box;display:flex;height:100%}.tox .tox-sv-palette-spectrum{height:100%}.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width:225px}.tox .tox-sv-palette-thumb{background:0 0;border:1px solid #000;border-radius:50%;box-sizing:content-box;height:12px;position:absolute;width:12px}.tox .tox-sv-palette-inner-thumb{border:1px solid #fff;border-radius:50%;height:10px;position:absolute;width:10px}.tox .tox-hue-slider{box-sizing:border-box;height:100%;width:25px}.tox .tox-hue-slider-spectrum{background:linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);height:100%;width:100%}.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width:20px}.tox .tox-hue-slider-spectrum:focus,.tox .tox-sv-palette-spectrum:focus{outline:#08f solid}.tox .tox-hue-slider-thumb{background:#fff;border:1px solid #000;box-sizing:content-box;height:4px;width:100%}.tox .tox-rgb-form{display:flex;flex-direction:column;justify-content:space-between}.tox .tox-rgb-form div{align-items:center;display:flex;justify-content:space-between;margin-bottom:5px;width:inherit}.tox .tox-rgb-form input{width:6em}.tox .tox-rgb-form input.tox-invalid{border:1px solid red!important}.tox .tox-rgb-form .tox-rgba-preview{border:1px solid #000;flex-grow:2;margin-bottom:0}.tox:not([dir=rtl]) .tox-sv-palette{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left:-1px}.tox:not([dir=rtl]) .tox-rgb-form label{margin-right:.5em}.tox[dir=rtl] .tox-sv-palette{margin-left:15px}.tox[dir=rtl] .tox-hue-slider{margin-left:15px}.tox[dir=rtl] .tox-hue-slider-thumb{margin-right:-1px}.tox[dir=rtl] .tox-rgb-form label{margin-left:.5em}.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin:5px 0 6px 11px}.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{border:0;margin:-4px -4px}.tox .tox-swatches__row{display:flex}@media (forced-colors:active){.tox .tox-swatches__row{forced-color-adjust:none}}.tox .tox-swatch{height:30px;transition:transform .15s,box-shadow .15s;width:30px}.tox .tox-swatch:focus,.tox .tox-swatch:hover{box-shadow:0 0 0 1px rgba(127,127,127,.3) inset;transform:scale(.8)}.tox .tox-swatch--remove{align-items:center;display:flex;justify-content:center}.tox .tox-swatch--remove svg path{stroke:#e74c3c}.tox .tox-swatches__picker-btn{align-items:center;background-color:transparent;border:0;cursor:pointer;display:flex;height:30px;justify-content:center;outline:0;padding:0;width:30px}.tox .tox-swatches__picker-btn svg{fill:#222f3e;height:24px;width:24px}.tox .tox-swatches__picker-btn:hover{background:#f0f0f0}.tox div.tox-swatch:not(.tox-swatch--remove) svg{display:none;fill:#222f3e;height:24px;margin:calc((30px - 24px)/ 2) calc((30px - 24px)/ 2);width:24px}.tox div.tox-swatch:not(.tox-swatch--remove) svg path{fill:#fff;paint-order:stroke;stroke:#222f3e;stroke-width:2px}.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg{display:block}.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left:auto}.tox[dir=rtl] .tox-swatches__picker-btn{margin-right:auto}.tox .tox-comment-thread{background:#fff;position:relative}.tox .tox-comment-thread>:not(:first-child){margin-top:8px}.tox .tox-comment{background:#fff;border:1px solid #eee;border-radius:6px;box-shadow:0 4px 8px 0 rgba(34,47,62,.1);padding:8px 8px 16px 8px;position:relative}.tox .tox-comment__header{align-items:center;color:#222f3e;display:flex;justify-content:space-between}.tox .tox-comment__date{color:#222f3e;font-size:12px;line-height:18px}.tox .tox-comment__body{color:#222f3e;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;margin-top:8px;position:relative;text-transform:initial}.tox .tox-comment__body textarea{resize:none;white-space:normal;width:100%}.tox .tox-comment__expander{padding-top:8px}.tox .tox-comment__expander p{color:rgba(34,47,62,.7);font-size:14px;font-style:normal}.tox .tox-comment__body p{margin:0}.tox .tox-comment__buttonspacing{padding-top:16px;text-align:center}.tox .tox-comment-thread__overlay::after{background:#fff;bottom:0;content:\"\";display:flex;left:0;opacity:.9;position:absolute;right:0;top:0;z-index:5}.tox .tox-comment__reply{display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;margin-top:8px}.tox .tox-comment__reply>:first-child{margin-bottom:8px;width:100%}.tox .tox-comment__edit{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:16px}.tox .tox-comment__gradient::after{background:linear-gradient(rgba(255,255,255,0),#fff);bottom:0;content:\"\";display:block;height:5em;margin-top:-40px;position:absolute;width:100%}.tox .tox-comment__overlay{background:#fff;bottom:0;display:flex;flex-direction:column;flex-grow:1;left:0;opacity:.9;position:absolute;right:0;text-align:center;top:0;z-index:5}.tox .tox-comment__loading-text{align-items:center;color:#222f3e;display:flex;flex-direction:column;position:relative}.tox .tox-comment__loading-text>div{padding-bottom:16px}.tox .tox-comment__overlaytext{bottom:0;flex-direction:column;font-size:14px;left:0;padding:1em;position:absolute;right:0;top:0;z-index:10}.tox .tox-comment__overlaytext p{background-color:#fff;box-shadow:0 0 8px 8px #fff;color:#222f3e;text-align:center}.tox .tox-comment__overlaytext div:nth-of-type(2){font-size:.8em}.tox .tox-comment__busy-spinner{align-items:center;background-color:#fff;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:20}.tox .tox-comment__scroll{display:flex;flex-direction:column;flex-shrink:1;overflow:auto}.tox .tox-conversations{margin:8px}.tox:not([dir=rtl]) .tox-comment__edit{margin-left:8px}.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left:8px}.tox[dir=rtl] .tox-comment__edit{margin-right:8px}.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right:8px}.tox .tox-user{align-items:center;display:flex}.tox .tox-user__avatar svg{fill:rgba(34,47,62,.7)}.tox .tox-user__avatar img{border-radius:50%;height:36px;object-fit:cover;vertical-align:middle;width:36px}.tox .tox-user__name{color:#222f3e;font-size:14px;font-style:normal;font-weight:700;line-height:18px;text-transform:none}.tox:not([dir=rtl]) .tox-user__avatar img,.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right:8px}.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left:8px}.tox[dir=rtl] .tox-user__avatar img,.tox[dir=rtl] .tox-user__avatar svg{margin-left:8px}.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right:8px}.tox .tox-dialog-wrap{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1100}.tox .tox-dialog-wrap__backdrop{background-color:rgba(255,255,255,.75);bottom:0;left:0;position:absolute;right:0;top:0;z-index:1}.tox .tox-dialog-wrap__backdrop--opaque{background-color:#fff}.tox .tox-dialog{background-color:#fff;border-color:#eee;border-radius:10px;border-style:solid;border-width:0;box-shadow:0 16px 16px -10px rgba(34,47,62,.15),0 0 40px 1px rgba(34,47,62,.15);display:flex;flex-direction:column;max-height:100%;max-width:480px;overflow:hidden;position:relative;width:95vw;z-index:2}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog{align-self:flex-start;margin:8px auto;max-height:calc(100vh - 8px * 2);width:calc(100vw - 16px)}}.tox .tox-dialog-inline{z-index:1100}.tox .tox-dialog__header{align-items:center;background-color:#fff;border-bottom:none;color:#222f3e;display:flex;font-size:16px;justify-content:space-between;padding:8px 16px 0 16px;position:relative}.tox .tox-dialog__header .tox-button{z-index:1}.tox .tox-dialog__draghandle{cursor:grab;height:100%;left:0;position:absolute;top:0;width:100%}.tox .tox-dialog__draghandle:active{cursor:grabbing}.tox .tox-dialog__dismiss{margin-left:auto}.tox .tox-dialog__title{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:20px;font-style:normal;font-weight:400;line-height:1.3;margin:0;text-transform:none}.tox .tox-dialog__body{color:#222f3e;display:flex;flex:1;font-size:16px;font-style:normal;font-weight:400;line-height:1.3;min-width:0;text-align:left;text-transform:none}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body{flex-direction:column}}.tox .tox-dialog__body-nav{align-items:flex-start;display:flex;flex-direction:column;flex-shrink:0;padding:16px 16px}@media only screen and (min-width:768px){.tox .tox-dialog__body-nav{max-width:11em}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body-nav{flex-direction:row;-webkit-overflow-scrolling:touch;overflow-x:auto;padding-bottom:0}}.tox .tox-dialog__body-nav-item{border-bottom:2px solid transparent;color:rgba(34,47,62,.7);display:inline-block;flex-shrink:0;font-size:14px;line-height:1.3;margin-bottom:8px;max-width:13em;text-decoration:none}.tox .tox-dialog__body-nav-item:focus{background-color:rgba(0,108,231,.1)}.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid #006ce7;color:#006ce7}@media (forced-colors:active){.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid highlight;color:highlight}}.tox .tox-dialog__body-content{box-sizing:border-box;display:flex;flex:1;flex-direction:column;max-height:min(650px,calc(100vh - 110px));overflow:auto;-webkit-overflow-scrolling:touch;padding:16px 16px}.tox .tox-dialog__body-content>*{margin-bottom:0;margin-top:16px}.tox .tox-dialog__body-content>:first-child{margin-top:0}.tox .tox-dialog__body-content>:last-child{margin-bottom:0}.tox .tox-dialog__body-content>:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content a{color:#006ce7;cursor:pointer;text-decoration:underline}.tox .tox-dialog__body-content a:focus,.tox .tox-dialog__body-content a:hover{color:#003c81;text-decoration:underline}.tox .tox-dialog__body-content a:focus-visible{border-radius:1px;outline:2px solid #006ce7;outline-offset:2px}.tox .tox-dialog__body-content a:active{color:#00244e;text-decoration:underline}.tox .tox-dialog__body-content svg{fill:#222f3e}.tox .tox-dialog__body-content strong{font-weight:700}.tox .tox-dialog__body-content ul{list-style-type:disc}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{padding-inline-start:2.5rem}.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{margin-bottom:16px}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content dt,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{display:block;margin-inline-end:0;margin-inline-start:0}.tox .tox-dialog__body-content .tox-form__group h1{color:#222f3e;font-size:20px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group h2{color:#222f3e;font-size:16px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group p{margin-bottom:16px}.tox .tox-dialog__body-content .tox-form__group h1:first-child,.tox .tox-dialog__body-content .tox-form__group h2:first-child,.tox .tox-dialog__body-content .tox-form__group p:first-child{margin-top:0}.tox .tox-dialog__body-content .tox-form__group h1:last-child,.tox .tox-dialog__body-content .tox-form__group h2:last-child,.tox .tox-dialog__body-content .tox-form__group p:last-child{margin-bottom:0}.tox .tox-dialog__body-content .tox-form__group h1:only-child,.tox .tox-dialog__body-content .tox-form__group h2:only-child,.tox .tox-dialog__body-content .tox-form__group p:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center{text-align:center}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end{text-align:end}.tox .tox-dialog--width-lg{height:650px;max-width:1200px}.tox .tox-dialog--fullscreen{height:100%;max-width:100%}.tox .tox-dialog--fullscreen .tox-dialog__body-content{max-height:100%}.tox .tox-dialog--width-md{max-width:800px}.tox .tox-dialog--width-md .tox-dialog__body-content{overflow:auto}.tox .tox-dialog__body-content--centered{text-align:center}.tox .tox-dialog__footer{align-items:center;background-color:#fff;border-top:none;display:flex;justify-content:space-between;padding:8px 16px}.tox .tox-dialog__footer-end,.tox .tox-dialog__footer-start{display:flex}.tox .tox-dialog__busy-spinner{align-items:center;background-color:rgba(255,255,255,.75);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:3}.tox .tox-dialog__table{border-collapse:collapse;width:100%}.tox .tox-dialog__table thead th{font-weight:700;padding-bottom:8px}.tox .tox-dialog__table thead th:first-child{padding-right:8px}.tox .tox-dialog__table tbody tr{border-bottom:1px solid #626262}.tox .tox-dialog__table tbody tr:last-child{border-bottom:none}.tox .tox-dialog__table td{padding-bottom:8px;padding-top:8px}.tox .tox-dialog__table td:first-child{padding-right:8px}.tox .tox-dialog__iframe{min-height:200px}.tox .tox-dialog__iframe.tox-dialog__iframe--opaque{background:#fff}.tox .tox-navobj-bordered{position:relative}.tox .tox-navobj-bordered::before{border:1px solid #eee;border-radius:6px;content:'';inset:0;opacity:1;pointer-events:none;position:absolute;z-index:1}.tox .tox-navobj-bordered iframe{border-radius:6px}.tox .tox-navobj-bordered-focus.tox-navobj-bordered::before{border-color:#006ce7;box-shadow:0 0 0 1px #006ce7;outline:0}.tox .tox-dialog__popups{position:absolute;width:100%;z-index:1100}.tox .tox-dialog__body-iframe{display:flex;flex:1;flex-direction:column}.tox .tox-dialog__body-iframe .tox-navobj{display:flex;flex:1}.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex:1;height:100%}.tox .tox-dialog-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-dialog-dock-fadein{opacity:1;visibility:visible}.tox .tox-dialog-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein{transition-delay:0s}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child){margin-left:8px}}.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left:8px}.tox[dir=rtl] .tox-dialog__body{text-align:right}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav{margin-left:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child){margin-right:8px}}.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right:8px}body.tox-dialog__disable-scroll{overflow:hidden}.tox .tox-dropzone-container{display:flex;flex:1}.tox .tox-dropzone{align-items:center;background:#fff;border:2px dashed #eee;box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;min-height:100px;padding:10px}.tox .tox-dropzone p{color:rgba(34,47,62,.7);margin:0 0 16px 0}.tox .tox-edit-area{display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-edit-area::before{border:2px solid #006ce7;border-radius:4px;content:'';inset:0;opacity:0;pointer-events:none;position:absolute;transition:opacity .15s;z-index:1}@media (forced-colors:active){.tox .tox-edit-area::before{border:2px solid highlight}}.tox .tox-edit-area__iframe{background-color:#fff;border:0;box-sizing:border-box;flex:1;height:100%;position:absolute;width:100%}.tox.tox-edit-focus .tox-edit-area::before{opacity:1}.tox.tox-inline-edit-area{border:1px dotted #eee}.tox .tox-editor-container{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-editor-header{display:grid;grid-template-columns:1fr min-content;z-index:2}.tox:not(.tox-tinymce-inline) .tox-editor-header{background-color:#fff;border-bottom:none;box-shadow:0 2px 2px -2px rgba(34,47,62,.1),0 8px 8px -4px rgba(34,47,62,.07);padding:4px 0}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition){transition:box-shadow .5s}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header{border-top:1px solid #e3e3e3;box-shadow:none}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:#fff;box-shadow:0 2px 2px -2px rgba(34,47,62,.2),0 8px 8px -4px rgba(34,47,62,.15);padding:4px 0}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header{box-shadow:0 2px 2px -2px rgba(34,47,62,.2),0 8px 8px -4px rgba(34,47,62,.15)}.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty{background:0 0;border:none;box-shadow:none;padding:0}.tox-editor-dock-fadeout{opacity:0;visibility:hidden}.tox-editor-dock-fadein{opacity:1;visibility:visible}.tox-editor-dock-transition{transition:visibility 0s linear .25s,opacity .25s ease}.tox-editor-dock-transition.tox-editor-dock-fadein{transition-delay:0s}.tox .tox-control-wrap{flex:1;position:relative}.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display:none}.tox .tox-control-wrap svg{display:block}.tox .tox-control-wrap__status-icon-wrap{position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-control-wrap__status-icon-invalid svg{fill:#c00}.tox .tox-control-wrap__status-icon-unknown svg{fill:orange}.tox .tox-control-wrap__status-icon-valid svg{fill:green}.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right:32px}.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right:4px}.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left:32px}.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left:4px}.tox .tox-custom-preview{border-color:#eee;border-radius:6px;border-style:solid;border-width:1px;flex:1;padding:8px}.tox .tox-autocompleter{max-width:25em}.tox .tox-autocompleter .tox-menu{box-sizing:border-box;max-width:25em}.tox .tox-autocompleter .tox-autocompleter-highlight{font-weight:700}.tox .tox-color-input{display:flex;position:relative;z-index:1}.tox .tox-color-input .tox-textfield{z-index:-1}.tox .tox-color-input span{border-color:rgba(34,47,62,.2);border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;height:24px;position:absolute;top:6px;width:24px}@media (forced-colors:active){.tox .tox-color-input span{border-color:currentColor;border-width:2px!important;forced-color-adjust:none}}.tox .tox-color-input span:focus:not([aria-disabled=true]),.tox .tox-color-input span:hover:not([aria-disabled=true]){border-color:#006ce7;cursor:pointer}.tox .tox-color-input span::before{background-image:linear-gradient(45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.25) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.25) 75%);background-position:0 0,0 6px,6px -6px,-6px 0;background-size:12px 12px;border:1px solid #fff;border-radius:6px;box-sizing:border-box;content:'';height:24px;left:-1px;position:absolute;top:-1px;width:24px;z-index:-1}@media (forced-colors:active){.tox .tox-color-input span::before{border:none}}.tox .tox-color-input span[aria-disabled=true]{cursor:not-allowed}.tox:not([dir=rtl]) .tox-color-input .tox-textfield{padding-left:36px}.tox:not([dir=rtl]) .tox-color-input span{left:6px}.tox[dir=rtl] .tox-color-input .tox-textfield{padding-right:36px}.tox[dir=rtl] .tox-color-input span{right:6px}.tox .tox-label,.tox .tox-toolbar-label{color:rgba(34,47,62,.7);display:block;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;padding:0 8px 0 0;text-transform:none;white-space:nowrap}.tox .tox-toolbar-label{padding:0 8px}.tox[dir=rtl] .tox-label{padding:0 0 0 8px}.tox .tox-form{display:flex;flex:1;flex-direction:column}.tox .tox-form__group{box-sizing:border-box;margin-bottom:4px}.tox .tox-form-group--maximize{flex:1}.tox .tox-form__group--error{color:#c00}.tox .tox-form__group--collection{display:flex}.tox .tox-form__grid{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.tox .tox-form__grid--2col>.tox-form__group{width:calc(50% - (8px / 2))}.tox .tox-form__grid--3col>.tox-form__group{width:calc(100% / 3 - (8px / 2))}.tox .tox-form__grid--4col>.tox-form__group{width:calc(25% - (8px / 2))}.tox .tox-form__controls-h-stack{align-items:center;display:flex}.tox .tox-form__group--inline{align-items:center;display:flex}.tox .tox-form__group--stretched{display:flex;flex:1;flex-direction:column}.tox .tox-form__group--stretched .tox-textarea{flex:1}.tox .tox-form__group--stretched .tox-navobj{display:flex;flex:1}.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex:1;height:100%}.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right:4px}.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display:none}.tox .tox-listboxfield .tox-listbox--select,.tox .tox-textarea,.tox .tox-textarea-wrap .tox-textarea:focus,.tox .tox-textfield,.tox .tox-toolbar-textfield{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#eee;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#222f3e;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 5.5px;resize:none;width:100%}.tox .tox-textarea[disabled],.tox .tox-textfield[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-custom-editor:focus-within,.tox .tox-listboxfield .tox-listbox--select:focus,.tox .tox-textarea-wrap:focus-within,.tox .tox-textarea:focus,.tox .tox-textfield:focus{background-color:#fff;border-color:#006ce7;box-shadow:0 0 0 1px #006ce7;outline:0}.tox .tox-toolbar-textfield{border-width:0;margin-bottom:3px;margin-top:2px;max-width:250px}.tox .tox-naked-btn{background-color:transparent;border:0;border-color:transparent;box-shadow:unset;color:#006ce7;cursor:pointer;display:block;margin:0;padding:0}.tox .tox-naked-btn svg{display:block;fill:#222f3e}.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left:4px}.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right:4px}.tox .tox-listboxfield{cursor:pointer;position:relative}.tox .tox-listboxfield .tox-listbox--select[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-listbox__select-label{cursor:default;flex:1;margin:0 4px}.tox .tox-listbox__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-listbox__select-chevron svg{fill:#222f3e}@media (forced-colors:active){.tox .tox-listbox__select-chevron svg{fill:currentColor!important}}.tox .tox-listboxfield .tox-listbox--select{align-items:center;display:flex}.tox:not([dir=rtl]) .tox-listboxfield svg{right:8px}.tox[dir=rtl] .tox-listboxfield svg{left:8px}.tox .tox-selectfield{cursor:pointer;position:relative}.tox .tox-selectfield select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#eee;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#222f3e;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 5.5px;resize:none;width:100%}.tox .tox-selectfield select[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-selectfield select::-ms-expand{display:none}.tox .tox-selectfield select:focus{background-color:#fff;border-color:#006ce7;box-shadow:0 0 0 1px #006ce7;outline:0}.tox .tox-selectfield svg{pointer-events:none;position:absolute;top:50%;transform:translateY(-50%)}.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"]{padding-right:24px}.tox:not([dir=rtl]) .tox-selectfield svg{right:8px}.tox[dir=rtl] .tox-selectfield select[size=\"0\"],.tox[dir=rtl] .tox-selectfield select[size=\"1\"]{padding-left:24px}.tox[dir=rtl] .tox-selectfield svg{left:8px}.tox .tox-textarea-wrap{border-color:#eee;border-radius:6px;border-style:solid;border-width:1px;display:flex;flex:1;overflow:hidden}.tox .tox-textarea{-webkit-appearance:textarea;-moz-appearance:textarea;appearance:textarea;white-space:pre-wrap}.tox .tox-textarea-wrap .tox-textarea{border:none}.tox .tox-textarea-wrap .tox-textarea:focus{border:none}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}.tox .tox-help__more-link{list-style:none;margin-top:1em}.tox .tox-imagepreview{background-color:#666;height:380px;overflow:hidden;position:relative;width:100%}.tox .tox-imagepreview.tox-imagepreview__loaded{overflow:auto}.tox .tox-imagepreview__container{display:flex;left:100vw;position:absolute;top:100vw}.tox .tox-imagepreview__image{background:url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==)}.tox .tox-image-tools .tox-spacer{flex:1}.tox .tox-image-tools .tox-bar{align-items:center;display:flex;height:60px;justify-content:center}.tox .tox-image-tools .tox-imagepreview,.tox .tox-image-tools .tox-imagepreview+.tox-bar{margin-top:8px}.tox .tox-image-tools .tox-croprect-block{background:#000;opacity:.5;position:absolute;zoom:1}.tox .tox-image-tools .tox-croprect-handle{border:2px solid #fff;height:20px;left:0;position:absolute;top:0;width:20px}.tox .tox-image-tools .tox-croprect-handle-move{border:0;cursor:move;position:absolute}.tox .tox-image-tools .tox-croprect-handle-nw{border-width:2px 0 0 2px;cursor:nw-resize;left:100px;margin:-2px 0 0 -2px;top:100px}.tox .tox-image-tools .tox-croprect-handle-ne{border-width:2px 2px 0 0;cursor:ne-resize;left:200px;margin:-2px 0 0 -20px;top:100px}.tox .tox-image-tools .tox-croprect-handle-sw{border-width:0 0 2px 2px;cursor:sw-resize;left:100px;margin:-20px 2px 0 -2px;top:200px}.tox .tox-image-tools .tox-croprect-handle-se{border-width:0 2px 2px 0;cursor:se-resize;left:200px;margin:-20px 0 0 -20px;top:200px}.tox .tox-insert-table-picker{background-color:#fff;display:flex;flex-wrap:wrap;width:170px}.tox .tox-insert-table-picker>div{border-color:#eee;border-style:solid;border-width:0 1px 1px 0;box-sizing:border-box;height:17px;width:17px}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:-4px -4px}.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color:#006ce7;border-color:#eee}@media (forced-colors:active){.tox .tox-insert-table-picker .tox-insert-table-picker__selected{border-color:Highlight;filter:contrast(50%)}}.tox .tox-insert-table-picker__label{color:rgba(34,47,62,.7);display:block;font-size:14px;padding:4px;text-align:center;width:100%}.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right:0}.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right:0}.tox .tox-menu{background-color:#fff;border:1px solid transparent;border-radius:6px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);display:inline-block;overflow:hidden;vertical-align:top;z-index:1150}.tox .tox-menu.tox-collection.tox-collection--list{padding:0 4px}.tox .tox-menu.tox-collection.tox-collection--toolbar{padding:8px}.tox .tox-menu.tox-collection.tox-collection--grid{padding:8px}@media only screen and (min-width:768px){.tox .tox-menu .tox-collection__item-label{overflow-wrap:break-word;word-break:normal}.tox .tox-dialog__popups .tox-menu .tox-collection__item-label{word-break:break-all}}.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin:0}.tox .tox-menubar{background:repeating-linear-gradient(transparent 0 1px,transparent 1px 39px) center top 39px/100% calc(100% - 39px) no-repeat;background-color:#fff;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;grid-column:1/-1;grid-row:1;padding:0 11px 0 12px}.tox .tox-promotion+.tox-menubar{grid-column:1}.tox .tox-promotion{background:repeating-linear-gradient(transparent 0 1px,transparent 1px 39px) center top 39px/100% calc(100% - 39px) no-repeat;background-color:#fff;grid-column:2;grid-row:1;padding-inline-end:8px;padding-inline-start:4px;padding-top:5px}.tox .tox-promotion-link{align-items:unsafe center;background-color:#e8f1f8;border-radius:5px;color:#086be6;cursor:pointer;display:flex;font-size:14px;height:26.6px;padding:4px 8px;white-space:nowrap}.tox .tox-promotion-link:hover{background-color:#b4d7ff}.tox .tox-promotion-link:focus{background-color:#d9edf7}.tox .tox-mbtn{align-items:center;background:#fff;border:0;border-radius:3px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;justify-content:center;margin:5px 1px 6px 0;outline:0;padding:0 4px;text-transform:none;width:auto}.tox .tox-mbtn[disabled]{background-color:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-mbtn:focus:not(:disabled){background:#fff;border:0;box-shadow:none;color:#222f3e;position:relative;z-index:1}.tox .tox-mbtn:focus:not(:disabled)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-mbtn:focus:not(:disabled)::after{border:2px solid highlight}}.tox .tox-mbtn--active,.tox .tox-mbtn:not(:disabled).tox-mbtn--active:focus{background:#a6ccf7;border:0;box-shadow:none;color:#222f3e}.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active){background:#f0f0f0;border:0;box-shadow:none;color:#222f3e}.tox .tox-mbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor:not-allowed}.tox .tox-mbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px;display:none}.tox .tox-notification{border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;display:grid;font-size:14px;font-weight:400;grid-template-columns:minmax(40px,1fr) auto minmax(40px,1fr);margin-left:auto;margin-right:auto;margin-top:4px;opacity:0;padding:4px;transition:transform .1s ease-in,opacity 150ms ease-in;width:-moz-max-content;width:max-content}.tox .tox-notification a{cursor:pointer;text-decoration:underline}.tox .tox-notification p{font-size:14px;font-weight:400}.tox .tox-notification:focus{border-color:#006ce7;box-shadow:0 0 0 1px #006ce7}.tox .tox-notification--in{opacity:1}.tox .tox-notification--success{background-color:#e4eeda;border-color:#d7e6c8;color:#222f3e}.tox .tox-notification--success p{color:#222f3e}.tox .tox-notification--success a{color:#517342}.tox .tox-notification--success a:focus,.tox .tox-notification--success a:hover{color:#24321d;text-decoration:underline}.tox .tox-notification--success a:focus-visible{border-radius:1px;outline:2px solid #517342;outline-offset:2px}.tox .tox-notification--success a:active{color:#0d120a;text-decoration:underline}.tox .tox-notification--success svg{fill:#222f3e}.tox .tox-notification--error{background-color:#f5cccc;border-color:#f0b3b3;color:#222f3e}.tox .tox-notification--error p{color:#222f3e}.tox .tox-notification--error a{color:#77181f}.tox .tox-notification--error a:focus,.tox .tox-notification--error a:hover{color:#220709;text-decoration:underline}.tox .tox-notification--error a:focus-visible{border-radius:1px;outline:2px solid #77181f;outline-offset:2px}.tox .tox-notification--error a:active{color:#000;text-decoration:underline}.tox .tox-notification--error svg{fill:#222f3e}.tox .tox-notification--warn,.tox .tox-notification--warning{background-color:#fff5cc;border-color:#fff0b3;color:#222f3e}.tox .tox-notification--warn p,.tox .tox-notification--warning p{color:#222f3e}.tox .tox-notification--warn a,.tox .tox-notification--warning a{color:#7a6e25}.tox .tox-notification--warn a:focus,.tox .tox-notification--warn a:hover,.tox .tox-notification--warning a:focus,.tox .tox-notification--warning a:hover{color:#2c280d;text-decoration:underline}.tox .tox-notification--warn a:focus-visible,.tox .tox-notification--warning a:focus-visible{border-radius:1px;outline:2px solid #7a6e25;outline-offset:2px}.tox .tox-notification--warn a:active,.tox .tox-notification--warning a:active{color:#050502;text-decoration:underline}.tox .tox-notification--warn svg,.tox .tox-notification--warning svg{fill:#222f3e}.tox .tox-notification--info{background-color:#d6e7fb;border-color:#c1dbf9;color:#222f3e}.tox .tox-notification--info p{color:#222f3e}.tox .tox-notification--info a{color:#2a64a6}.tox .tox-notification--info a:focus,.tox .tox-notification--info a:hover{color:#163355;text-decoration:underline}.tox .tox-notification--info a:focus-visible{border-radius:1px;outline:2px solid #2a64a6;outline-offset:2px}.tox .tox-notification--info a:active{color:#0b1a2c;text-decoration:underline}.tox .tox-notification--info svg{fill:#222f3e}.tox .tox-notification__body{align-self:center;color:#222f3e;font-size:14px;grid-column-end:3;grid-column-start:2;grid-row-end:2;grid-row-start:1;text-align:center;white-space:normal;word-break:break-all;word-break:break-word}.tox .tox-notification__body>*{margin:0}.tox .tox-notification__body>*+*{margin-top:1rem}.tox .tox-notification__icon{align-self:center;grid-column-end:2;grid-column-start:1;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification__icon svg{display:block}.tox .tox-notification__dismiss{align-self:start;grid-column-end:4;grid-column-start:3;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification .tox-progress-bar{grid-column-end:4;grid-column-start:1;grid-row-end:3;grid-row-start:2;justify-self:center}.tox .tox-notification-container-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-notification-container-dock-fadein{opacity:1;visibility:visible}.tox .tox-notification-container-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-notification-container-dock-transition.tox-notification-container-dock-fadein{transition-delay:0s}.tox .tox-pop{display:inline-block;position:relative}.tox .tox-pop--resizing{transition:width .1s ease}.tox .tox-pop--resizing .tox-toolbar,.tox .tox-pop--resizing .tox-toolbar__group{flex-wrap:nowrap}.tox .tox-pop--transition{transition:.15s ease;transition-property:left,right,top,bottom}.tox .tox-pop--transition::after,.tox .tox-pop--transition::before{transition:all .15s,visibility 0s,opacity 75ms ease 75ms}.tox .tox-pop__dialog{background-color:#fff;border:1px solid #eee;border-radius:6px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);min-width:0;overflow:hidden}.tox .tox-pop__dialog>:not(.tox-toolbar){margin:4px 4px 4px 8px}.tox .tox-pop__dialog .tox-toolbar{background-color:transparent;margin-bottom:-1px}.tox .tox-pop::after,.tox .tox-pop::before{border-style:solid;content:'';display:block;height:0;opacity:1;position:absolute;width:0}@media (forced-colors:active){.tox .tox-pop::after,.tox .tox-pop::before{content:none}}.tox .tox-pop.tox-pop--inset::after,.tox .tox-pop.tox-pop--inset::before{opacity:0;transition:all 0s .15s,visibility 0s,opacity 75ms ease}.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{left:50%;top:100%}.tox .tox-pop.tox-pop--bottom::after{border-color:#fff transparent transparent transparent;border-width:8px;margin-left:-8px;margin-top:-1px}.tox .tox-pop.tox-pop--bottom::before{border-color:#eee transparent transparent transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{left:50%;top:0;transform:translateY(-100%)}.tox .tox-pop.tox-pop--top::after{border-color:transparent transparent #fff transparent;border-width:8px;margin-left:-8px;margin-top:1px}.tox .tox-pop.tox-pop--top::before{border-color:transparent transparent #eee transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{left:0;top:calc(50% - 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--left::after{border-color:transparent #fff transparent transparent;border-width:8px;margin-left:-15px}.tox .tox-pop.tox-pop--left::before{border-color:transparent #eee transparent transparent;border-width:10px;margin-left:-19px}.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{left:100%;top:calc(50% + 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--right::after{border-color:transparent transparent transparent #fff;border-width:8px;margin-left:-1px}.tox .tox-pop.tox-pop--right::before{border-color:transparent transparent transparent #eee;border-width:10px;margin-left:-1px}.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left:20px}.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left:calc(100% - 20px)}.tox .tox-sidebar-wrap{display:flex;flex-direction:row;flex-grow:1;min-height:0}.tox .tox-sidebar{background-color:#fff;display:flex;flex-direction:row;justify-content:flex-end}.tox .tox-sidebar__slider{display:flex;overflow:hidden}.tox .tox-sidebar__pane-container{display:flex}.tox .tox-sidebar__pane{display:flex}.tox .tox-sidebar--sliding-closed{opacity:0}.tox .tox-sidebar--sliding-open{opacity:1}.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition:width .5s ease,opacity .5s ease}.tox .tox-selector{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;display:inline-block;height:10px;position:absolute;width:10px}.tox.tox-platform-touch .tox-selector{height:12px;width:12px}.tox .tox-slider{align-items:center;display:flex;flex:1;height:24px;justify-content:center;position:relative}.tox .tox-slider__rail{background-color:transparent;border:1px solid #eee;border-radius:6px;height:10px;min-width:120px;width:100%}.tox .tox-slider__handle{background-color:#006ce7;border:2px solid #0054b4;border-radius:6px;box-shadow:none;height:24px;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:14px}.tox .tox-form__controls-h-stack>.tox-slider:not(:first-of-type){margin-inline-start:8px}.tox .tox-form__controls-h-stack>.tox-form__group+.tox-slider{margin-inline-start:32px}.tox .tox-form__controls-h-stack>.tox-slider+.tox-form__group{margin-inline-start:32px}.tox .tox-source-code{overflow:auto}.tox .tox-spinner{display:flex}.tox .tox-spinner>div{animation:tam-bouncing-dots 1.5s ease-in-out 0s infinite both;background-color:rgba(34,47,62,.7);border-radius:100%;height:8px;width:8px}.tox .tox-spinner>div:nth-child(1){animation-delay:-.32s}.tox .tox-spinner>div:nth-child(2){animation-delay:-.16s}@keyframes tam-bouncing-dots{0%,100%,80%{transform:scale(0)}40%{transform:scale(1)}}.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right:4px}.tox .tox-statusbar{align-items:center;background-color:#fff;border-top:1px solid #e3e3e3;color:rgba(34,47,62,.7);display:flex;flex:0 0 auto;font-size:14px;font-weight:400;height:25px;overflow:hidden;padding:0 8px;position:relative;text-transform:none}.tox .tox-statusbar__path{display:flex;flex:1 1 auto;text-overflow:ellipsis;white-space:nowrap}.tox .tox-statusbar__right-container{display:flex;justify-content:flex-end;white-space:nowrap}.tox .tox-statusbar__help-text{text-align:center}.tox .tox-statusbar__text-container{align-items:flex-start;display:flex;flex:1 1 auto;height:16px;justify-content:space-between;overflow:hidden}@media only screen and (min-width:768px){.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__help-text,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__path,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__right-container{flex:0 0 calc(100% / 3)}}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-end{justify-content:flex-end}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-start{justify-content:flex-start}.tox .tox-statusbar__text-container.tox-statusbar__text-container--space-around{justify-content:space-around}.tox .tox-statusbar__path>*{display:inline;white-space:nowrap}.tox .tox-statusbar__wordcount{flex:0 0 auto;margin-left:1ch}@media only screen and (max-width:767px){.tox .tox-statusbar__text-container .tox-statusbar__help-text{display:none}.tox .tox-statusbar__text-container .tox-statusbar__help-text:only-child{display:block}}.tox .tox-statusbar a,.tox .tox-statusbar__path-item,.tox .tox-statusbar__wordcount{color:rgba(34,47,62,.7);position:relative;text-decoration:none}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){color:#222f3e;cursor:pointer}.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border:2px solid highlight}}.tox .tox-statusbar__branding svg{fill:rgba(34,47,62,.8);height:1em;margin-left:.3em;width:auto}@media (forced-colors:active){.tox .tox-statusbar__branding svg{fill:currentColor}}.tox .tox-statusbar__branding a{align-items:center;display:inline-flex}.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg,.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg{fill:#222f3e}.tox .tox-statusbar__resize-handle{align-items:flex-end;align-self:stretch;cursor:nwse-resize;display:flex;flex:0 0 auto;justify-content:flex-end;margin-bottom:3px;margin-left:4px;margin-right:calc(3px - 8px);margin-top:3px;padding-bottom:0;padding-left:0;padding-right:0;position:relative}.tox .tox-statusbar__resize-handle svg{display:block;fill:rgba(34,47,62,.5)}.tox .tox-statusbar__resize-handle:focus svg,.tox .tox-statusbar__resize-handle:hover svg{fill:#222f3e}.tox .tox-statusbar__resize-handle:focus-visible{background-color:transparent;border-radius:1px 1px 5px 1px;box-shadow:0 0 0 2px transparent}.tox .tox-statusbar__resize-handle:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar__resize-handle:focus-visible::after{border:2px solid highlight}}.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right:4px}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:2ch}.tox[dir=rtl] .tox-statusbar{flex-direction:row-reverse}.tox[dir=rtl] .tox-statusbar__path>*{margin-left:4px}.tox[dir=rtl] .tox-statusbar__branding svg{margin-left:0;margin-right:.3em}.tox .tox-throbber{z-index:1299}.tox .tox-throbber__busy-spinner{align-items:center;background-color:rgba(255,255,255,.6);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.tox .tox-tbtn{align-items:center;background:#fff;border:0;border-radius:3px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;justify-content:center;margin:6px 1px 5px 0;outline:0;padding:0;text-transform:none;width:34px}@media (forced-colors:active){.tox .tox-tbtn.tox-tbtn:hover,.tox .tox-tbtn:hover{outline:1px dashed currentColor}.tox .tox-tbtn.tox-tbtn--active,.tox .tox-tbtn.tox-tbtn--enabled,.tox .tox-tbtn.tox-tbtn--enabled:focus,.tox .tox-tbtn.tox-tbtn--enabled:hover,.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){outline:1px solid currentColor;position:relative}}.tox .tox-tbtn svg{display:block;fill:#222f3e}@media (forced-colors:active){.tox .tox-tbtn svg{fill:currentColor!important}.tox .tox-tbtn svg.tox-tbtn--enabled,.tox .tox-tbtn svg:focus:not(.tox-tbtn--disabled){fill:currentColor!important}.tox .tox-tbtn svg .tox-tbtn:disabled,.tox .tox-tbtn svg .tox-tbtn:disabled:hover,.tox .tox-tbtn svg.tox-tbtn--disabled,.tox .tox-tbtn svg.tox-tbtn--disabled:hover{filter:contrast(0)}}.tox .tox-tbtn.tox-tbtn-more{padding-left:5px;padding-right:5px;width:inherit}.tox .tox-tbtn:focus{background:#fff;border:0;box-shadow:none;position:relative;z-index:1}.tox .tox-tbtn:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn:focus::after{border:2px solid highlight}}.tox .tox-tbtn:hover{background:#f0f0f0;border:0;box-shadow:none;color:#222f3e}.tox .tox-tbtn:hover svg{fill:#222f3e}.tox .tox-tbtn:active{background:#a6ccf7;border:0;box-shadow:none;color:#222f3e}.tox .tox-tbtn:active svg{fill:#222f3e}.tox .tox-tbtn--disabled .tox-tbtn--enabled svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{background:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--active,.tox .tox-tbtn--enabled,.tox .tox-tbtn--enabled:focus,.tox .tox-tbtn--enabled:hover{background:#a6ccf7;border:0;box-shadow:none;color:#222f3e;position:relative}.tox .tox-tbtn--active>*,.tox .tox-tbtn--enabled:focus>*,.tox .tox-tbtn--enabled:hover>*,.tox .tox-tbtn--enabled>*{transform:none}.tox .tox-tbtn--active svg,.tox .tox-tbtn--enabled svg,.tox .tox-tbtn--enabled:focus svg,.tox .tox-tbtn--enabled:hover svg{fill:#222f3e}.tox .tox-tbtn--active.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:focus.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--enabled:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn--enabled:focus::after{border:2px solid highlight}}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){color:#222f3e}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg{fill:#222f3e}.tox .tox-tbtn:active>*{transform:none}.tox .tox-tbtn--md{height:42px;width:51px}.tox .tox-tbtn--lg{flex-direction:column;height:56px;width:68px}.tox .tox-tbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tbtn--labeled{padding:0 4px;width:unset}.tox .tox-tbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-number-input{background:#f7f7f7;border-radius:3px;display:flex;margin:6px 1px 5px 0;position:relative;width:auto}.tox .tox-number-input:focus{background:#f7f7f7}.tox .tox-number-input:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper{display:flex;pointer-events:none;position:relative;text-align:center}.tox .tox-number-input .tox-input-wrapper:focus{background-color:#f7f7f7;z-index:1}.tox .tox-number-input .tox-input-wrapper:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border:2px solid highlight}}.tox .tox-number-input input{border-radius:3px;color:#222f3e;font-size:14px;margin:2px 0;pointer-events:all;position:relative;width:60px}.tox .tox-number-input input:hover{background:#f0f0f0;color:#222f3e}.tox .tox-number-input input:focus{background-color:#f7f7f7}.tox .tox-number-input input:disabled{background:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-number-input button{color:#222f3e;height:28px;position:relative;text-align:center;width:24px}@media (forced-colors:active){.tox .tox-number-input button:active,.tox .tox-number-input button:focus,.tox .tox-number-input button:hover{outline:1px solid currentColor!important}}.tox .tox-number-input button svg{display:block;fill:#222f3e;margin:0 auto;transform:scale(.67)}@media (forced-colors:active){.tox .tox-number-input button svg,.tox .tox-number-input button svg:active,.tox .tox-number-input button svg:hover{fill:currentColor!important}.tox .tox-number-input button svg:disabled{filter:contrast(0)}}.tox .tox-number-input button:focus{background:#f7f7f7;z-index:1}.tox .tox-number-input button:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input button:focus::after{border:2px solid highlight}}.tox .tox-number-input button:hover{background:#f0f0f0;border:0;box-shadow:none;color:#222f3e}.tox .tox-number-input button:hover svg{fill:#222f3e}.tox .tox-number-input button:active{background:#a6ccf7;border:0;box-shadow:none;color:#222f3e}.tox .tox-number-input button:active svg{fill:#222f3e}.tox .tox-number-input button:disabled{background:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-number-input button:disabled svg{fill:rgba(34,47,62,.5)}.tox .tox-number-input button.minus{border-radius:3px 0 0 3px}.tox .tox-number-input button.plus{border-radius:0 3px 3px 0}.tox .tox-number-input:focus:not(:active)>.tox-input-wrapper,.tox .tox-number-input:focus:not(:active)>button{background:#f7f7f7}.tox .tox-tbtn--select{margin:6px 1px 5px 0;padding:0 4px;width:auto}.tox .tox-tbtn__select-label{cursor:default;font-weight:400;height:initial;margin:0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-tbtn__select-chevron svg{fill:rgba(34,47,62,.5)}@media (forced-colors:active){.tox .tox-tbtn__select-chevron svg{fill:currentColor}}.tox .tox-tbtn--bespoke{background:#f7f7f7}.tox .tox-tbtn--bespoke:focus{background:#f7f7f7}.tox .tox-tbtn--bespoke+.tox-tbtn--bespoke{margin-inline-start:4px}.tox .tox-tbtn--bespoke .tox-tbtn__select-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:7em}.tox .tox-tbtn--disabled .tox-tbtn__select-label,.tox .tox-tbtn--select:disabled .tox-tbtn__select-label{cursor:not-allowed}.tox .tox-split-button{border:0;border-radius:3px;box-sizing:border-box;display:flex;margin:6px 1px 5px 0}.tox .tox-split-button:hover{box-shadow:0 0 0 1px #f0f0f0 inset}.tox .tox-split-button:focus{background:#fff;box-shadow:none;color:#222f3e;position:relative;z-index:1}.tox .tox-split-button:focus::after{pointer-events:none;border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-split-button:focus::after{border:2px solid highlight}}.tox .tox-split-button>*{border-radius:0}.tox .tox-split-button>:nth-child(1){border-bottom-left-radius:3px;border-top-left-radius:3px}.tox .tox-split-button>:nth-child(2){border-bottom-right-radius:3px;border-top-right-radius:3px}.tox .tox-split-button__chevron{width:16px}.tox .tox-split-button__chevron svg{fill:rgba(34,47,62,.5)}@media (forced-colors:active){.tox .tox-split-button__chevron svg{fill:currentColor}}.tox .tox-split-button .tox-tbtn{margin:0}.tox .tox-split-button:focus .tox-tbtn{background-color:transparent}.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{background:#fff;box-shadow:none;color:rgba(34,47,62,.5)}.tox.tox-platform-touch .tox-split-button .tox-tbtn--select{padding:0 0}.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child{width:30px}.tox.tox-platform-touch .tox-split-button__chevron{width:20px}.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color,.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color{opacity:.6}.tox .tox-toolbar-overlord{background-color:#fff}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background-attachment:local;background-color:#fff;background-image:repeating-linear-gradient(#e3e3e3 0 1px,transparent 1px 39px);background-position:center top 40px;background-repeat:no-repeat;background-size:calc(100% - 11px * 2) calc(100% - 41px);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 0;transform:perspective(1px)}.tox .tox-toolbar-overlord>.tox-toolbar,.tox .tox-toolbar-overlord>.tox-toolbar__overflow,.tox .tox-toolbar-overlord>.tox-toolbar__primary{background-position:center top 0;background-size:calc(100% - 11px * 2) calc(100% - 0px)}.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height:0;opacity:0;padding-bottom:0;padding-top:0;visibility:hidden}.tox .tox-toolbar__overflow--growing{transition:height .3s ease,opacity .2s linear .1s}.tox .tox-toolbar__overflow--shrinking{transition:opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s}.tox .tox-anchorbar,.tox .tox-toolbar-overlord{grid-column:1/-1}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{border-top:1px solid transparent;margin-top:-1px;padding-bottom:1px;padding-top:1px}@media (forced-colors:active){.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{outline:1px solid currentColor}}.tox .tox-toolbar--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-pop .tox-toolbar{border-width:0}.tox .tox-toolbar--no-divider{background-image:none}.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,.tox .tox-toolbar-overlord .tox-toolbar__primary{background-position:center top 39px}.tox .tox-editor-header>.tox-toolbar--scrolling,.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child{background-image:none}.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color:#fff;background-position:center top 43px;background-size:calc(100% - 8px * 2) calc(100% - 51px);border:none;border-radius:6px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);overscroll-behavior:none;padding:4px 0}@media (forced-colors:active){.tox.tox-tinymce-aux .tox-toolbar__overflow{border:solid}}.tox-pop .tox-pop__dialog .tox-toolbar{background-position:center top 43px;background-size:calc(100% - 11px * 2) calc(100% - 51px);padding:4px 0}.tox .tox-toolbar__group{align-items:center;display:flex;flex-wrap:wrap;margin:0 0;padding:0 11px 0 12px}.tox .tox-toolbar__group--pull-right{margin-left:auto}.tox .tox-toolbar--scrolling .tox-toolbar__group{flex-shrink:0;flex-wrap:nowrap}.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right:1px solid transparent}.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left:1px solid transparent}.tox .tox-tooltip{display:inline-block;max-width:15em;padding:8px;pointer-events:none;position:relative;width:-moz-max-content;width:max-content;z-index:1150}.tox .tox-tooltip__body{background-color:#222f3e;border-radius:6px;box-shadow:none;color:#fff;font-size:12px;font-style:normal;font-weight:600;overflow-wrap:break-word;padding:4px 6px;text-transform:none}@media (forced-colors:active){.tox .tox-tooltip__body{outline:outset 1px}}.tox .tox-tooltip__arrow{position:absolute}.tox .tox-tooltip--down .tox-tooltip__arrow{border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #222f3e;bottom:0;left:50%;position:absolute;transform:translateX(-50%)}.tox .tox-tooltip--up .tox-tooltip__arrow{border-bottom:8px solid #222f3e;border-left:8px solid transparent;border-right:8px solid transparent;left:50%;position:absolute;top:0;transform:translateX(-50%)}.tox .tox-tooltip--right .tox-tooltip__arrow{border-bottom:8px solid transparent;border-left:8px solid #222f3e;border-top:8px solid transparent;position:absolute;right:0;top:50%;transform:translateY(-50%)}.tox .tox-tooltip--left .tox-tooltip__arrow{border-bottom:8px solid transparent;border-right:8px solid #222f3e;border-top:8px solid transparent;left:0;position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-tree{display:flex;flex-direction:column}.tox .tox-tree .tox-trbtn{align-items:center;background:0 0;border:0;border-radius:4px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;margin-bottom:4px;margin-top:4px;outline:0;overflow:hidden;padding:0;padding-left:8px;text-transform:none}.tox .tox-tree .tox-trbtn .tox-tree__label{cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tree .tox-trbtn svg{display:block;fill:#222f3e}.tox .tox-tree .tox-trbtn:focus{background:#f0f0f0;border:0;box-shadow:none}.tox .tox-tree .tox-trbtn:hover{background:#f0f0f0;border:0;box-shadow:none;color:#222f3e}.tox .tox-tree .tox-trbtn:hover svg{fill:#222f3e}.tox .tox-tree .tox-trbtn:active{background:#a6ccf7;border:0;box-shadow:none;color:#222f3e}.tox .tox-tree .tox-trbtn:active svg{fill:#222f3e}.tox .tox-tree .tox-trbtn--disabled,.tox .tox-tree .tox-trbtn--disabled:hover,.tox .tox-tree .tox-trbtn:disabled,.tox .tox-tree .tox-trbtn:disabled:hover{background:0 0;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-tree .tox-trbtn--disabled svg,.tox .tox-tree .tox-trbtn--disabled:hover svg,.tox .tox-tree .tox-trbtn:disabled svg,.tox .tox-tree .tox-trbtn:disabled:hover svg{fill:rgba(34,47,62,.5)}.tox .tox-tree .tox-trbtn--enabled,.tox .tox-tree .tox-trbtn--enabled:hover{background:#a6ccf7;border:0;box-shadow:none;color:#222f3e}.tox .tox-tree .tox-trbtn--enabled:hover>*,.tox .tox-tree .tox-trbtn--enabled>*{transform:none}.tox .tox-tree .tox-trbtn--enabled svg,.tox .tox-tree .tox-trbtn--enabled:hover svg{fill:#222f3e}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled){color:#222f3e}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg{fill:#222f3e}.tox .tox-tree .tox-trbtn:active>*{transform:none}.tox .tox-tree .tox-trbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tree .tox-trbtn--labeled{padding:0 4px;width:unset}.tox .tox-tree .tox-trbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-tree .tox-tree--directory{display:flex;flex-direction:column}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label{font-weight:700}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron{margin-right:6px}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--shrinking) .tox-chevron{transition:transform .5s ease-in-out}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--open) .tox-chevron{transform:rotate(90deg)}.tox .tox-tree .tox-tree--leaf__label{font-weight:400}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg{fill:#222f3e}.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg{fill:#222f3e}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#222f3e}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory__children{overflow:hidden;padding-left:16px}.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking{transition:height .5s ease-in-out}.tox .tox-tree .tox-trbtn.tox-tree--leaf__label{display:flex;justify-content:space-between}.tox .tox-revisionhistory__pane{padding:0!important}.tox .tox-revisionhistory__container{display:flex;flex-direction:column;height:100%}.tox .tox-revisionhistory{background-color:#fff;border-radius:4px;border-top:1px solid #eee;display:flex;flex:1;height:100%;margin-top:8px;overflow-x:auto;overflow-y:hidden;position:relative;width:100%}.tox .tox-revisionhistory--align-right{margin-left:auto}.tox .tox-revisionhistory__iframe{flex:1}.tox .tox-revisionhistory__sidebar{border-left:1px solid #eee;height:100%;max-width:360px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__sidebar-title{border-bottom:1px solid #eee;color:#222f3e;font-size:20px;font-weight:400;height:60px;min-width:192px;padding:16px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions{flex-direction:column;max-height:calc(100% - 60px);min-width:192px;overflow-y:auto;padding:8px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus{height:100%;position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0;border-radius:6px;bottom:1px;left:1px;right:1px;top:1px}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card{border:1px solid #eee;border-radius:6px;color:#222f3e;cursor:pointer;font-size:14px;margin-bottom:8px;padding:8px;text-overflow:ellipsis;text-wrap:nowrap;width:100%}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:hover{background-color:#f0f0f0;box-shadow:none;color:#222f3e}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus{position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border-radius:6px!important;border-radius:3px;bottom:0;box-shadow:0 0 0 2px #006ce7;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card.tox-revisionhistory__card--selected{background-color:#a6ccf7;box-shadow:none;color:#222f3e}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__norevision{color:rgba(34,47,62,.7);font-size:16px;line-height:24px;padding:5px 5.5px}.tox .tox-view-wrap,.tox .tox-view-wrap__slot-container{background-color:#fff;display:flex;flex:1;flex-direction:column;height:100%}.tox .tox-view{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-view__header{align-items:center;display:flex;font-size:16px;justify-content:space-between;padding:10px 10px 2px 10px;position:relative}.tox .tox-view__label{color:#222f3e;font-weight:700;line-height:24px;padding:4px 16px;text-align:center;white-space:nowrap}.tox .tox-view__label--normal{font-size:16px}.tox .tox-view__label--large{font-size:20px}.tox .tox-view--mobile.tox-view__header,.tox .tox-view--mobile.tox-view__toolbar{padding:8px}.tox .tox-view--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-view__toolbar{display:flex;flex-direction:row;gap:8px;justify-content:space-between;overflow-x:auto;padding:10px 10px 2px 10px}.tox .tox-view__toolbar__group{display:flex;flex-direction:row;gap:12px}.tox .tox-view__header-end,.tox .tox-view__header-start{display:flex}.tox .tox-view__pane{height:100%;padding:8px;position:relative;width:100%}.tox .tox-view__pane_panel{border:1px solid #eee;border-radius:6px}.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end>*,.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start>*{margin-left:8px}.tox[dir=rtl] .tox-view__header .tox-view__header-end>*,.tox[dir=rtl] .tox-view__header .tox-view__header-start>*{margin-right:8px}.tox .tox-well{border:1px solid #eee;border-radius:6px;padding:8px;width:100%}.tox .tox-well>:first-child{margin-top:0}.tox .tox-well>:last-child{margin-bottom:0}.tox .tox-well>:only-child{margin:0}.tox .tox-custom-editor{border:1px solid #eee;border-radius:6px;display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-dialog-loading::before{background-color:rgba(0,0,0,.5);content:\"\";height:100%;position:absolute;width:100%;z-index:1000}.tox .tox-tab{cursor:pointer}.tox .tox-dialog__content-js{display:flex;flex:1}.tox .tox-dialog__body-content .tox-collection{display:flex;flex:1}") //# sourceMappingURL=skin.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide/skin.shadowdom.js ================================================ tinymce.Resource.add('ui/default/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}") //# sourceMappingURL=skin.shadowdom.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide-dark/content.inline.js ================================================ tinymce.Resource.add('ui/dark/content.inline.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.1)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.1);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}") //# sourceMappingURL=content.inline.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide-dark/content.js ================================================ tinymce.Resource.add('ui/dark/content.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%236d737b%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#282a36}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#6272a4}.token.punctuation{color:#f8f8f2}.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#ff79c6}.token.boolean,.token.number{color:#bd93f9}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#50fa7b}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#f1fa8c}.token.keyword{color:#8be9fd}.token.important,.token.regex{color:#ffb86c}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.3)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.3);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #4099ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #4099ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #4099ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #4099ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #4099ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#4099ff}.mce-content-body .mce-edit-focus{outline:3px solid #4099ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid transparent;bottom:-1px;content:'';left:-1px;mix-blend-mode:lighten;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#4099ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}body{font-family:sans-serif}table{border-collapse:collapse}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide-dark/skin.js ================================================ tinymce.Resource.add('ui/dark/skin.css', ".tox{box-shadow:none;box-sizing:content-box;color:#222f3e;cursor:auto;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-style:normal;font-weight:400;line-height:normal;-webkit-tap-highlight-color:transparent;text-decoration:none;text-shadow:none;text-transform:none;vertical-align:initial;white-space:normal}.tox :not(svg):not(rect){box-sizing:inherit;color:inherit;cursor:inherit;direction:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:inherit;-webkit-tap-highlight-color:inherit;text-align:inherit;text-decoration:inherit;text-shadow:inherit;text-transform:inherit;vertical-align:inherit;white-space:inherit}.tox :not(svg):not(rect){background:0 0;border:0;box-shadow:none;float:none;height:auto;margin:0;max-width:none;outline:0;padding:0;position:static;width:auto}.tox:not([dir=rtl]){direction:ltr;text-align:left}.tox[dir=rtl]{direction:rtl;text-align:right}.tox-tinymce{border:2px solid #161f29;border-radius:10px;box-shadow:none;box-sizing:border-box;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;overflow:hidden;position:relative;visibility:inherit!important}.tox.tox-tinymce-inline{border:none;box-shadow:none;overflow:initial}.tox.tox-tinymce-inline .tox-editor-container{overflow:initial}.tox.tox-tinymce-inline .tox-editor-header{background-color:#222f3e;border:2px solid #161f29;border-radius:10px;box-shadow:none;overflow:hidden}.tox-tinymce-aux{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;z-index:1300}.tox-tinymce :focus,.tox-tinymce-aux :focus{outline:0}button::-moz-focus-inner{border:0}.tox[dir=rtl] .tox-icon--flip svg{transform:rotateY(180deg)}.tox .accessibility-issue__header{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description{align-items:stretch;border-radius:6px;display:flex;justify-content:space-between}.tox .accessibility-issue__description>div{padding-bottom:4px}.tox .accessibility-issue__description>div>div{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description>div>div .tox-icon svg{display:block}.tox .accessibility-issue__repair{margin-top:16px}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description{background-color:rgba(0,101,216,.4);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon{background-color:#006ce7;color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover{background-color:#0060ce}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active{background-color:#0054b4}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description{background-color:rgba(255,165,0,.5);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon{background-color:#ffe89d;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover{background-color:#f2d574;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active{background-color:#e8c657;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description{background-color:rgba(204,0,0,.5);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon{background-color:#f2bfbf;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover{background-color:#e9a4a4;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active{background-color:#ee9494;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description{background-color:rgba(120,171,70,.5);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description>:last-child{display:none}.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2{font-size:14px;margin-top:0}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-left:4px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-left:auto}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description{padding:4px 4px 4px 8px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-right:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-right:auto}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description{padding:4px 8px 4px 4px}.tox .mce-codemirror{background:#fff;bottom:0;font-size:13px;left:0;position:absolute;right:0;top:0;z-index:1}.tox .mce-codemirror.tox-inline-codemirror{margin:8px;position:absolute}.tox .tox-advtemplate .tox-form__grid{flex:1}.tox .tox-advtemplate .tox-form__grid>div:first-child{display:flex;flex-direction:column;width:30%}.tox .tox-advtemplate .tox-form__grid>div:first-child>div:nth-child(2){flex-basis:0;flex-grow:1;overflow:auto}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid>div:first-child{width:100%}}.tox .tox-advtemplate iframe{border-color:#161f29;border-radius:10px;border-style:solid;border-width:1px;margin:0 10px}.tox .tox-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bottom-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bar{display:flex;flex:0 0 auto}.tox .tox-button{background-color:#006ce7;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#006ce7;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;line-height:24px;margin:0;outline:0;padding:4px 16px;position:relative;text-align:center;text-decoration:none;text-transform:none;white-space:nowrap}.tox .tox-button::before{border-radius:6px;bottom:-1px;box-shadow:inset 0 0 0 1px #fff,0 0 0 2px #006ce7;content:'';left:-1px;opacity:0;pointer-events:none;position:absolute;right:-1px;top:-1px}.tox .tox-button[disabled]{background-color:#006ce7;background-image:none;border-color:#006ce7;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button:focus:not(:disabled){background-color:#0060ce;background-image:none;border-color:#0060ce;box-shadow:none;color:#fff}.tox .tox-button:focus:not(:disabled)::before{opacity:1}.tox .tox-button:hover:not(:disabled){background-color:#0060ce;background-image:none;border-color:#0060ce;box-shadow:none;color:#fff}.tox .tox-button:active:not(:disabled){background-color:#0054b4;background-image:none;border-color:#0054b4;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled{background-color:#0054b4;background-image:none;border-color:#0054b4;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled[disabled]{background-color:#0054b4;background-image:none;border-color:#0054b4;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button.tox-button--enabled:focus:not(:disabled){background-color:#00489b;background-image:none;border-color:#00489b;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:hover:not(:disabled){background-color:#00489b;background-image:none;border-color:#00489b;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:active:not(:disabled){background-color:#003c81;background-image:none;border-color:#003c81;box-shadow:none;color:#fff}.tox .tox-button--icon-and-text,.tox .tox-button.tox-button--icon-and-text,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text{display:flex;padding:5px 4px}.tox .tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg{display:block;fill:currentColor}.tox .tox-button--secondary{background-color:#3d546f;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#3d546f;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;color:#fff;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;outline:0;padding:4px 16px;text-decoration:none;text-transform:none}.tox .tox-button--secondary[disabled]{background-color:#3d546f;background-image:none;border-color:#3d546f;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-button--secondary:focus:not(:disabled){background-color:#34485f;background-image:none;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--secondary:hover:not(:disabled){background-color:#34485f;background-image:none;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--secondary:active:not(:disabled){background-color:#2b3b4e;background-image:none;border-color:#2b3b4e;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled{background-color:#2b5c93;background-image:none;border-color:#2b5c93;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled[disabled]{background-color:#2b5c93;background-image:none;border-color:#2b5c93;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled){background-color:#254f80;background-image:none;border-color:#254f80;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled){background-color:#254f80;background-image:none;border-color:#254f80;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled){background-color:#1f436c;background-image:none;border-color:#1f436c;box-shadow:none;color:#fff}.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding:4px}.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display:block;fill:currentColor}.tox .tox-button-link{background:0;border:none;box-sizing:border-box;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;white-space:nowrap}.tox .tox-button-link--sm{font-size:14px}.tox .tox-button--naked{background-color:transparent;border-color:transparent;box-shadow:unset;color:#fff}.tox .tox-button--naked[disabled]{background-color:rgba(255,255,255,.2);border-color:transparent;box-shadow:unset;color:rgba(255,255,255,.5)}.tox .tox-button--naked:hover:not(:disabled){background-color:rgba(255,255,255,.2);border-color:transparent;box-shadow:unset;color:#fff}.tox .tox-button--naked:focus:not(:disabled){background-color:rgba(255,255,255,.2);border-color:transparent;box-shadow:unset;color:#fff}.tox .tox-button--naked:active:not(:disabled){background-color:rgba(255,255,255,.3);border-color:transparent;box-shadow:unset;color:#fff}.tox .tox-button--naked .tox-icon svg{fill:currentColor}.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color:#fff}.tox .tox-checkbox{align-items:center;border-radius:6px;cursor:pointer;display:flex;height:36px;min-width:36px}.tox .tox-checkbox__input{height:1px;overflow:hidden;position:absolute;top:auto;width:1px}.tox .tox-checkbox__icons{align-items:center;border-radius:6px;box-shadow:0 0 0 2px transparent;box-sizing:content-box;display:flex;height:24px;justify-content:center;padding:calc(4px - 1px);width:24px}.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:block;fill:rgba(255,255,255,.2)}@media (forced-colors:active){.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:currentColor!important}}.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:none;fill:#006ce7}.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display:none;fill:#006ce7}.tox .tox-checkbox--disabled{color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg{fill:rgba(255,255,255,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:rgba(255,255,255,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:rgba(255,255,255,.5)}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display:block}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:block}.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:6px;box-shadow:inset 0 0 0 1px #006ce7;padding:calc(4px - 1px)}.tox:not([dir=rtl]) .tox-checkbox__label{margin-left:4px}.tox:not([dir=rtl]) .tox-checkbox__input{left:-10000px}.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left:4px}.tox[dir=rtl] .tox-checkbox__label{margin-right:4px}.tox[dir=rtl] .tox-checkbox__input{right:-10000px}.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right:4px}.tox .tox-collection--toolbar .tox-collection__group{display:flex;padding:0}.tox .tox-collection--grid .tox-collection__group{display:flex;flex-wrap:wrap;max-height:208px;overflow-x:hidden;overflow-y:auto;padding:0}.tox .tox-collection--list .tox-collection__group{border-bottom-width:0;border-color:rgba(255,255,255,.15);border-left-width:0;border-right-width:0;border-style:solid;border-top-width:1px;padding:4px 0}.tox .tox-collection--list .tox-collection__group:first-child{border-top-width:0}.tox .tox-collection__group-heading{background-color:rgba(255,255,255,.15);color:rgba(255,255,255,.5);cursor:default;font-size:12px;font-style:normal;font-weight:400;margin-bottom:4px;margin-top:-4px;padding:4px 8px;text-transform:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection__item{align-items:center;border-radius:3px;color:#fff;display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection--list .tox-collection__item{padding:4px 8px}.tox .tox-collection--toolbar .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--grid .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--list .tox-collection__item--enabled{background-color:#2b3b4e;color:#fff}.tox .tox-collection--list .tox-collection__item--active{background-color:#006ce7}.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{background-color:#599fef;color:#fff}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active{background-color:#2b3b4e;position:relative}.tox .tox-collection--toolbar .tox-collection__item--active:hover{background-color:#2f4055;color:#fff}.tox .tox-collection--toolbar .tox-collection__item--active:focus{background-color:#2f4055;color:#fff}.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border:2px solid highlight}}.tox .tox-collection--grid .tox-collection__item--enabled{background-color:#599fef;color:#fff}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:#2f4055;color:#fff;position:relative;z-index:1}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff inset;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border:2px solid highlight}}.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#fff}@media (forced-colors:active){.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){border:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#fff}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled):hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection__item-checkmark,.tox .tox-collection__item-icon{align-items:center;display:flex;height:24px;justify-content:center;width:24px}.tox .tox-collection__item-checkmark svg,.tox .tox-collection__item-icon svg{fill:currentColor}.tox .tox-collection--toolbar-lg .tox-collection__item-icon{height:48px;width:48px}.tox .tox-collection__item-label{color:currentColor;display:inline-block;flex:1;font-size:14px;font-style:normal;font-weight:400;line-height:24px;max-width:100%;text-transform:none;word-break:break-all}.tox .tox-collection__item-accessory{color:currentColor;display:inline-block;font-size:14px;height:24px;line-height:24px;text-transform:none}.tox .tox-collection__item-caret{align-items:center;display:flex;min-height:24px}.tox .tox-collection__item-caret::after{content:'';font-size:0;min-height:inherit}.tox .tox-collection__item-caret svg{fill:currentColor}.tox .tox-collection__item--state-disabled{background-color:transparent;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg{fill:rgba(255,255,255,.5)}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display:none}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory+.tox-collection__item-checkmark{display:none}.tox .tox-collection--horizontal{background-color:#2b3b4e;border:1px solid rgba(255,255,255,.15);border-radius:6px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:nowrap;margin-bottom:0;overflow-x:auto;padding:0}.tox .tox-collection--horizontal .tox-collection__group{align-items:center;display:flex;flex-wrap:nowrap;margin:0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item{height:28px;margin:6px 1px 5px 0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item-label{white-space:nowrap}.tox .tox-collection--horizontal .tox-collection__item-caret{margin-left:4px}.tox .tox-collection__item-container{display:flex}.tox .tox-collection__item-container--row{align-items:center;flex:1 1 auto;flex-direction:row}.tox .tox-collection__item-container--row.tox-collection__item-container--align-left{margin-right:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--align-right{justify-content:flex-end;margin-left:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top{align-items:flex-start;margin-bottom:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle{align-items:center}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom{align-items:flex-end;margin-top:auto}.tox .tox-collection__item-container--column{align-self:center;flex:1 1 auto;flex-direction:column}.tox .tox-collection__item-container--column.tox-collection__item-container--align-left{align-items:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--align-right{align-items:flex-end}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top{align-self:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle{align-self:center}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom{align-self:flex-end}.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-right:1px solid transparent}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left:8px}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-left:4px}.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left:16px;text-align:right}.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret{margin-left:16px}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-left:1px solid transparent}.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right:8px}.tox[dir=rtl] .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-right:4px}.tox[dir=rtl] .tox-collection__item-accessory{margin-right:16px;text-align:left}.tox[dir=rtl] .tox-collection .tox-collection__item-caret{margin-right:16px;transform:rotateY(180deg)}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret{margin-right:4px}@media (forced-colors:active){.tox .tox-hue-slider,.tox .tox-rgb-form .tox-rgba-preview{background-color:currentColor!important;border:1px solid highlight!important;forced-color-adjust:none}}.tox .tox-color-picker-container{display:flex;flex-direction:row;height:225px;margin:0}.tox .tox-sv-palette{box-sizing:border-box;display:flex;height:100%}.tox .tox-sv-palette-spectrum{height:100%}.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width:225px}.tox .tox-sv-palette-thumb{background:0 0;border:1px solid #000;border-radius:50%;box-sizing:content-box;height:12px;position:absolute;width:12px}.tox .tox-sv-palette-inner-thumb{border:1px solid #fff;border-radius:50%;height:10px;position:absolute;width:10px}.tox .tox-hue-slider{box-sizing:border-box;height:100%;width:25px}.tox .tox-hue-slider-spectrum{background:linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);height:100%;width:100%}.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width:20px}.tox .tox-hue-slider-spectrum:focus,.tox .tox-sv-palette-spectrum:focus{outline:#08f solid}.tox .tox-hue-slider-thumb{background:#fff;border:1px solid #000;box-sizing:content-box;height:4px;width:100%}.tox .tox-rgb-form{display:flex;flex-direction:column;justify-content:space-between}.tox .tox-rgb-form div{align-items:center;display:flex;justify-content:space-between;margin-bottom:5px;width:inherit}.tox .tox-rgb-form input{width:6em}.tox .tox-rgb-form input.tox-invalid{border:1px solid red!important}.tox .tox-rgb-form .tox-rgba-preview{border:1px solid #000;flex-grow:2;margin-bottom:0}.tox:not([dir=rtl]) .tox-sv-palette{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left:-1px}.tox:not([dir=rtl]) .tox-rgb-form label{margin-right:.5em}.tox[dir=rtl] .tox-sv-palette{margin-left:15px}.tox[dir=rtl] .tox-hue-slider{margin-left:15px}.tox[dir=rtl] .tox-hue-slider-thumb{margin-right:-1px}.tox[dir=rtl] .tox-rgb-form label{margin-left:.5em}.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin:5px 0 6px 11px}.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{border:0;margin:-4px -4px}.tox .tox-swatches__row{display:flex}@media (forced-colors:active){.tox .tox-swatches__row{forced-color-adjust:none}}.tox .tox-swatch{height:30px;transition:transform .15s,box-shadow .15s;width:30px}.tox .tox-swatch:focus,.tox .tox-swatch:hover{box-shadow:0 0 0 1px rgba(127,127,127,.3) inset;transform:scale(.8)}.tox .tox-swatch--remove{align-items:center;display:flex;justify-content:center}.tox .tox-swatch--remove svg path{stroke:#e74c3c}.tox .tox-swatches__picker-btn{align-items:center;background-color:transparent;border:0;cursor:pointer;display:flex;height:30px;justify-content:center;outline:0;padding:0;width:30px}.tox .tox-swatches__picker-btn svg{fill:#fff;height:24px;width:24px}.tox .tox-swatches__picker-btn:hover{background:#2f4055}.tox div.tox-swatch:not(.tox-swatch--remove) svg{display:none;fill:#fff;height:24px;margin:calc((30px - 24px)/ 2) calc((30px - 24px)/ 2);width:24px}.tox div.tox-swatch:not(.tox-swatch--remove) svg path{fill:#fff;paint-order:stroke;stroke:#222f3e;stroke-width:2px}.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg{display:block}.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left:auto}.tox[dir=rtl] .tox-swatches__picker-btn{margin-right:auto}.tox .tox-comment-thread{background:#2b3b4e;position:relative}.tox .tox-comment-thread>:not(:first-child){margin-top:8px}.tox .tox-comment{background:#2b3b4e;border:1px solid #161f29;border-radius:6px;box-shadow:0 4px 8px 0 rgba(34,47,62,.1);padding:8px 8px 16px 8px;position:relative}.tox .tox-comment__header{align-items:center;color:#fff;display:flex;justify-content:space-between}.tox .tox-comment__date{color:#fff;font-size:12px;line-height:18px}.tox .tox-comment__body{color:#fff;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;margin-top:8px;position:relative;text-transform:initial}.tox .tox-comment__body textarea{resize:none;white-space:normal;width:100%}.tox .tox-comment__expander{padding-top:8px}.tox .tox-comment__expander p{color:rgba(255,255,255,.5);font-size:14px;font-style:normal}.tox .tox-comment__body p{margin:0}.tox .tox-comment__buttonspacing{padding-top:16px;text-align:center}.tox .tox-comment-thread__overlay::after{background:#2b3b4e;bottom:0;content:\"\";display:flex;left:0;opacity:.9;position:absolute;right:0;top:0;z-index:5}.tox .tox-comment__reply{display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;margin-top:8px}.tox .tox-comment__reply>:first-child{margin-bottom:8px;width:100%}.tox .tox-comment__edit{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:16px}.tox .tox-comment__gradient::after{background:linear-gradient(rgba(43,59,78,0),#2b3b4e);bottom:0;content:\"\";display:block;height:5em;margin-top:-40px;position:absolute;width:100%}.tox .tox-comment__overlay{background:#2b3b4e;bottom:0;display:flex;flex-direction:column;flex-grow:1;left:0;opacity:.9;position:absolute;right:0;text-align:center;top:0;z-index:5}.tox .tox-comment__loading-text{align-items:center;color:#fff;display:flex;flex-direction:column;position:relative}.tox .tox-comment__loading-text>div{padding-bottom:16px}.tox .tox-comment__overlaytext{bottom:0;flex-direction:column;font-size:14px;left:0;padding:1em;position:absolute;right:0;top:0;z-index:10}.tox .tox-comment__overlaytext p{background-color:#2b3b4e;box-shadow:0 0 8px 8px #2b3b4e;color:#fff;text-align:center}.tox .tox-comment__overlaytext div:nth-of-type(2){font-size:.8em}.tox .tox-comment__busy-spinner{align-items:center;background-color:#2b3b4e;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:20}.tox .tox-comment__scroll{display:flex;flex-direction:column;flex-shrink:1;overflow:auto}.tox .tox-conversations{margin:8px}.tox:not([dir=rtl]) .tox-comment__edit{margin-left:8px}.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left:8px}.tox[dir=rtl] .tox-comment__edit{margin-right:8px}.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right:8px}.tox .tox-user{align-items:center;display:flex}.tox .tox-user__avatar svg{fill:rgba(255,255,255,.5)}.tox .tox-user__avatar img{border-radius:50%;height:36px;object-fit:cover;vertical-align:middle;width:36px}.tox .tox-user__name{color:#fff;font-size:14px;font-style:normal;font-weight:700;line-height:18px;text-transform:none}.tox:not([dir=rtl]) .tox-user__avatar img,.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right:8px}.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left:8px}.tox[dir=rtl] .tox-user__avatar img,.tox[dir=rtl] .tox-user__avatar svg{margin-left:8px}.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right:8px}.tox .tox-dialog-wrap{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1100}.tox .tox-dialog-wrap__backdrop{background-color:rgba(34,47,62,.75);bottom:0;left:0;position:absolute;right:0;top:0;z-index:1}.tox .tox-dialog-wrap__backdrop--opaque{background-color:#222f3e}.tox .tox-dialog{background-color:#2b3b4e;border-color:#161f29;border-radius:10px;border-style:solid;border-width:0;box-shadow:0 16px 16px -10px rgba(34,47,62,.15),0 0 40px 1px rgba(34,47,62,.15);display:flex;flex-direction:column;max-height:100%;max-width:480px;overflow:hidden;position:relative;width:95vw;z-index:2}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog{align-self:flex-start;margin:8px auto;max-height:calc(100vh - 8px * 2);width:calc(100vw - 16px)}}.tox .tox-dialog-inline{z-index:1100}.tox .tox-dialog__header{align-items:center;background-color:#2b3b4e;border-bottom:none;color:#fff;display:flex;font-size:16px;justify-content:space-between;padding:8px 16px 0 16px;position:relative}.tox .tox-dialog__header .tox-button{z-index:1}.tox .tox-dialog__draghandle{cursor:grab;height:100%;left:0;position:absolute;top:0;width:100%}.tox .tox-dialog__draghandle:active{cursor:grabbing}.tox .tox-dialog__dismiss{margin-left:auto}.tox .tox-dialog__title{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:20px;font-style:normal;font-weight:400;line-height:1.3;margin:0;text-transform:none}.tox .tox-dialog__body{color:#fff;display:flex;flex:1;font-size:16px;font-style:normal;font-weight:400;line-height:1.3;min-width:0;text-align:left;text-transform:none}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body{flex-direction:column}}.tox .tox-dialog__body-nav{align-items:flex-start;display:flex;flex-direction:column;flex-shrink:0;padding:16px 16px}@media only screen and (min-width:768px){.tox .tox-dialog__body-nav{max-width:11em}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body-nav{flex-direction:row;-webkit-overflow-scrolling:touch;overflow-x:auto;padding-bottom:0}}.tox .tox-dialog__body-nav-item{border-bottom:2px solid transparent;color:rgba(255,255,255,.5);display:inline-block;flex-shrink:0;font-size:14px;line-height:1.3;margin-bottom:8px;max-width:13em;text-decoration:none}.tox .tox-dialog__body-nav-item:focus{background-color:rgba(0,108,231,.1)}.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid #67aeff;color:#67aeff}@media (forced-colors:active){.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid highlight;color:highlight}}.tox .tox-dialog__body-content{box-sizing:border-box;display:flex;flex:1;flex-direction:column;max-height:min(650px,calc(100vh - 110px));overflow:auto;-webkit-overflow-scrolling:touch;padding:16px 16px}.tox .tox-dialog__body-content>*{margin-bottom:0;margin-top:16px}.tox .tox-dialog__body-content>:first-child{margin-top:0}.tox .tox-dialog__body-content>:last-child{margin-bottom:0}.tox .tox-dialog__body-content>:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content a{color:#67aeff;cursor:pointer;text-decoration:underline}.tox .tox-dialog__body-content a:focus,.tox .tox-dialog__body-content a:hover{color:#cde5ff;text-decoration:underline}.tox .tox-dialog__body-content a:focus-visible{border-radius:1px;outline:2px solid #67aeff;outline-offset:2px}.tox .tox-dialog__body-content a:active{color:#fff;text-decoration:underline}.tox .tox-dialog__body-content svg{fill:#fff}.tox .tox-dialog__body-content strong{font-weight:700}.tox .tox-dialog__body-content ul{list-style-type:disc}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{padding-inline-start:2.5rem}.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{margin-bottom:16px}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content dt,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{display:block;margin-inline-end:0;margin-inline-start:0}.tox .tox-dialog__body-content .tox-form__group h1{color:#fff;font-size:20px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group h2{color:#fff;font-size:16px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group p{margin-bottom:16px}.tox .tox-dialog__body-content .tox-form__group h1:first-child,.tox .tox-dialog__body-content .tox-form__group h2:first-child,.tox .tox-dialog__body-content .tox-form__group p:first-child{margin-top:0}.tox .tox-dialog__body-content .tox-form__group h1:last-child,.tox .tox-dialog__body-content .tox-form__group h2:last-child,.tox .tox-dialog__body-content .tox-form__group p:last-child{margin-bottom:0}.tox .tox-dialog__body-content .tox-form__group h1:only-child,.tox .tox-dialog__body-content .tox-form__group h2:only-child,.tox .tox-dialog__body-content .tox-form__group p:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center{text-align:center}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end{text-align:end}.tox .tox-dialog--width-lg{height:650px;max-width:1200px}.tox .tox-dialog--fullscreen{height:100%;max-width:100%}.tox .tox-dialog--fullscreen .tox-dialog__body-content{max-height:100%}.tox .tox-dialog--width-md{max-width:800px}.tox .tox-dialog--width-md .tox-dialog__body-content{overflow:auto}.tox .tox-dialog__body-content--centered{text-align:center}.tox .tox-dialog__footer{align-items:center;background-color:#2b3b4e;border-top:none;display:flex;justify-content:space-between;padding:8px 16px}.tox .tox-dialog__footer-end,.tox .tox-dialog__footer-start{display:flex}.tox .tox-dialog__busy-spinner{align-items:center;background-color:rgba(34,47,62,.75);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:3}.tox .tox-dialog__table{border-collapse:collapse;width:100%}.tox .tox-dialog__table thead th{font-weight:700;padding-bottom:8px}.tox .tox-dialog__table thead th:first-child{padding-right:8px}.tox .tox-dialog__table tbody tr{border-bottom:1px solid #000}.tox .tox-dialog__table tbody tr:last-child{border-bottom:none}.tox .tox-dialog__table td{padding-bottom:8px;padding-top:8px}.tox .tox-dialog__table td:first-child{padding-right:8px}.tox .tox-dialog__iframe{min-height:200px}.tox .tox-dialog__iframe.tox-dialog__iframe--opaque{background:#fff}.tox .tox-navobj-bordered{position:relative}.tox .tox-navobj-bordered::before{border:1px solid #161f29;border-radius:6px;content:'';inset:0;opacity:1;pointer-events:none;position:absolute;z-index:1}.tox .tox-navobj-bordered iframe{border-radius:6px}.tox .tox-navobj-bordered-focus.tox-navobj-bordered::before{border-color:#006ce7;box-shadow:0 0 0 1px #006ce7;outline:0}.tox .tox-dialog__popups{position:absolute;width:100%;z-index:1100}.tox .tox-dialog__body-iframe{display:flex;flex:1;flex-direction:column}.tox .tox-dialog__body-iframe .tox-navobj{display:flex;flex:1}.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex:1;height:100%}.tox .tox-dialog-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-dialog-dock-fadein{opacity:1;visibility:visible}.tox .tox-dialog-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein{transition-delay:0s}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child){margin-left:8px}}.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left:8px}.tox[dir=rtl] .tox-dialog__body{text-align:right}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav{margin-left:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child){margin-right:8px}}.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right:8px}body.tox-dialog__disable-scroll{overflow:hidden}.tox .tox-dropzone-container{display:flex;flex:1}.tox .tox-dropzone{align-items:center;background:#fff;border:2px dashed #161f29;box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;min-height:100px;padding:10px}.tox .tox-dropzone p{color:rgba(255,255,255,.5);margin:0 0 16px 0}.tox .tox-edit-area{display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-edit-area::before{border:2px solid #fff;border-radius:4px;content:'';inset:0;opacity:0;pointer-events:none;position:absolute;transition:opacity .15s;z-index:1}@media (forced-colors:active){.tox .tox-edit-area::before{border:2px solid highlight}}.tox .tox-edit-area__iframe{background-color:#fff;border:0;box-sizing:border-box;flex:1;height:100%;position:absolute;width:100%}.tox.tox-edit-focus .tox-edit-area::before{opacity:1}.tox.tox-inline-edit-area{border:1px dotted #161f29}.tox .tox-editor-container{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-editor-header{display:grid;grid-template-columns:1fr min-content;z-index:2}.tox:not(.tox-tinymce-inline) .tox-editor-header{background-color:#222f3e;border-bottom:1px solid rgba(255,255,255,.15);box-shadow:none;padding:4px 0}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition){transition:box-shadow .5s}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header{border-top:1px solid rgba(255,255,255,.15);box-shadow:none}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:#222f3e;box-shadow:none;padding:4px 0}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header{box-shadow:none}.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty{background:0 0;border:none;box-shadow:none;padding:0}.tox-editor-dock-fadeout{opacity:0;visibility:hidden}.tox-editor-dock-fadein{opacity:1;visibility:visible}.tox-editor-dock-transition{transition:visibility 0s linear .25s,opacity .25s ease}.tox-editor-dock-transition.tox-editor-dock-fadein{transition-delay:0s}.tox .tox-control-wrap{flex:1;position:relative}.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display:none}.tox .tox-control-wrap svg{display:block}.tox .tox-control-wrap__status-icon-wrap{position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-control-wrap__status-icon-invalid svg{fill:#c00}.tox .tox-control-wrap__status-icon-unknown svg{fill:orange}.tox .tox-control-wrap__status-icon-valid svg{fill:green}.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right:32px}.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right:4px}.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left:32px}.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left:4px}.tox .tox-custom-preview{border-color:#161f29;border-radius:6px;border-style:solid;border-width:1px;flex:1;padding:8px}.tox .tox-autocompleter{max-width:25em}.tox .tox-autocompleter .tox-menu{box-sizing:border-box;max-width:25em}.tox .tox-autocompleter .tox-autocompleter-highlight{font-weight:700}.tox .tox-color-input{display:flex;position:relative;z-index:1}.tox .tox-color-input .tox-textfield{z-index:-1}.tox .tox-color-input span{border-color:rgba(34,47,62,.2);border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;height:24px;position:absolute;top:6px;width:24px}@media (forced-colors:active){.tox .tox-color-input span{border-color:currentColor;border-width:2px!important;forced-color-adjust:none}}.tox .tox-color-input span:focus:not([aria-disabled=true]),.tox .tox-color-input span:hover:not([aria-disabled=true]){border-color:#006ce7;cursor:pointer}.tox .tox-color-input span::before{background-image:linear-gradient(45deg,rgba(255,255,255,.25) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.25) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.25) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.25) 75%);background-position:0 0,0 6px,6px -6px,-6px 0;background-size:12px 12px;border:1px solid #2b3b4e;border-radius:6px;box-sizing:border-box;content:'';height:24px;left:-1px;position:absolute;top:-1px;width:24px;z-index:-1}@media (forced-colors:active){.tox .tox-color-input span::before{border:none}}.tox .tox-color-input span[aria-disabled=true]{cursor:not-allowed}.tox:not([dir=rtl]) .tox-color-input .tox-textfield{padding-left:36px}.tox:not([dir=rtl]) .tox-color-input span{left:6px}.tox[dir=rtl] .tox-color-input .tox-textfield{padding-right:36px}.tox[dir=rtl] .tox-color-input span{right:6px}.tox .tox-label,.tox .tox-toolbar-label{color:rgba(255,255,255,.5);display:block;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;padding:0 8px 0 0;text-transform:none;white-space:nowrap}.tox .tox-toolbar-label{padding:0 8px}.tox[dir=rtl] .tox-label{padding:0 0 0 8px}.tox .tox-form{display:flex;flex:1;flex-direction:column}.tox .tox-form__group{box-sizing:border-box;margin-bottom:4px}.tox .tox-form-group--maximize{flex:1}.tox .tox-form__group--error{color:#c00}.tox .tox-form__group--collection{display:flex}.tox .tox-form__grid{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.tox .tox-form__grid--2col>.tox-form__group{width:calc(50% - (8px / 2))}.tox .tox-form__grid--3col>.tox-form__group{width:calc(100% / 3 - (8px / 2))}.tox .tox-form__grid--4col>.tox-form__group{width:calc(25% - (8px / 2))}.tox .tox-form__controls-h-stack{align-items:center;display:flex}.tox .tox-form__group--inline{align-items:center;display:flex}.tox .tox-form__group--stretched{display:flex;flex:1;flex-direction:column}.tox .tox-form__group--stretched .tox-textarea{flex:1}.tox .tox-form__group--stretched .tox-navobj{display:flex;flex:1}.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex:1;height:100%}.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right:4px}.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display:none}.tox .tox-listboxfield .tox-listbox--select,.tox .tox-textarea,.tox .tox-textarea-wrap .tox-textarea:focus,.tox .tox-textfield,.tox .tox-toolbar-textfield{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#2b3b4e;border-color:#161f29;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 5.5px;resize:none;width:100%}.tox .tox-textarea[disabled],.tox .tox-textfield[disabled]{background-color:#222f3e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-custom-editor:focus-within,.tox .tox-listboxfield .tox-listbox--select:focus,.tox .tox-textarea-wrap:focus-within,.tox .tox-textarea:focus,.tox .tox-textfield:focus{background-color:#2b3b4e;border-color:#006ce7;box-shadow:0 0 0 1px #006ce7;outline:0}.tox .tox-toolbar-textfield{border-width:0;margin-bottom:3px;margin-top:2px;max-width:250px}.tox .tox-naked-btn{background-color:transparent;border:0;border-color:transparent;box-shadow:unset;color:#006ce7;cursor:pointer;display:block;margin:0;padding:0}.tox .tox-naked-btn svg{display:block;fill:#fff}.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left:4px}.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right:4px}.tox .tox-listboxfield{cursor:pointer;position:relative}.tox .tox-listboxfield .tox-listbox--select[disabled]{background-color:#19232e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-listbox__select-label{cursor:default;flex:1;margin:0 4px}.tox .tox-listbox__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-listbox__select-chevron svg{fill:#fff}@media (forced-colors:active){.tox .tox-listbox__select-chevron svg{fill:currentColor!important}}.tox .tox-listboxfield .tox-listbox--select{align-items:center;display:flex}.tox:not([dir=rtl]) .tox-listboxfield svg{right:8px}.tox[dir=rtl] .tox-listboxfield svg{left:8px}.tox .tox-selectfield{cursor:pointer;position:relative}.tox .tox-selectfield select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#2b3b4e;border-color:#161f29;border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 5.5px;resize:none;width:100%}.tox .tox-selectfield select[disabled]{background-color:#19232e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-selectfield select::-ms-expand{display:none}.tox .tox-selectfield select:focus{background-color:#2b3b4e;border-color:#006ce7;box-shadow:0 0 0 1px #006ce7;outline:0}.tox .tox-selectfield svg{pointer-events:none;position:absolute;top:50%;transform:translateY(-50%)}.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"]{padding-right:24px}.tox:not([dir=rtl]) .tox-selectfield svg{right:8px}.tox[dir=rtl] .tox-selectfield select[size=\"0\"],.tox[dir=rtl] .tox-selectfield select[size=\"1\"]{padding-left:24px}.tox[dir=rtl] .tox-selectfield svg{left:8px}.tox .tox-textarea-wrap{border-color:#161f29;border-radius:6px;border-style:solid;border-width:1px;display:flex;flex:1;overflow:hidden}.tox .tox-textarea{-webkit-appearance:textarea;-moz-appearance:textarea;appearance:textarea;white-space:pre-wrap}.tox .tox-textarea-wrap .tox-textarea{border:none}.tox .tox-textarea-wrap .tox-textarea:focus{border:none}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}.tox .tox-help__more-link{list-style:none;margin-top:1em}.tox .tox-imagepreview{background-color:#666;height:380px;overflow:hidden;position:relative;width:100%}.tox .tox-imagepreview.tox-imagepreview__loaded{overflow:auto}.tox .tox-imagepreview__container{display:flex;left:100vw;position:absolute;top:100vw}.tox .tox-imagepreview__image{background:url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==)}.tox .tox-image-tools .tox-spacer{flex:1}.tox .tox-image-tools .tox-bar{align-items:center;display:flex;height:60px;justify-content:center}.tox .tox-image-tools .tox-imagepreview,.tox .tox-image-tools .tox-imagepreview+.tox-bar{margin-top:8px}.tox .tox-image-tools .tox-croprect-block{background:#000;opacity:.5;position:absolute;zoom:1}.tox .tox-image-tools .tox-croprect-handle{border:2px solid #fff;height:20px;left:0;position:absolute;top:0;width:20px}.tox .tox-image-tools .tox-croprect-handle-move{border:0;cursor:move;position:absolute}.tox .tox-image-tools .tox-croprect-handle-nw{border-width:2px 0 0 2px;cursor:nw-resize;left:100px;margin:-2px 0 0 -2px;top:100px}.tox .tox-image-tools .tox-croprect-handle-ne{border-width:2px 2px 0 0;cursor:ne-resize;left:200px;margin:-2px 0 0 -20px;top:100px}.tox .tox-image-tools .tox-croprect-handle-sw{border-width:0 0 2px 2px;cursor:sw-resize;left:100px;margin:-20px 2px 0 -2px;top:200px}.tox .tox-image-tools .tox-croprect-handle-se{border-width:0 2px 2px 0;cursor:se-resize;left:200px;margin:-20px 0 0 -20px;top:200px}.tox .tox-insert-table-picker{background-color:#222f3e;display:flex;flex-wrap:wrap;width:170px}.tox .tox-insert-table-picker>div{border-color:rgba(255,255,255,.15);border-style:solid;border-width:0 1px 1px 0;box-sizing:border-box;height:17px;width:17px}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:-4px -4px}.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color:#006ce7;border-color:rgba(255,255,255,.15)}@media (forced-colors:active){.tox .tox-insert-table-picker .tox-insert-table-picker__selected{border-color:Highlight;filter:contrast(50%)}}.tox .tox-insert-table-picker__label{color:#fff;display:block;font-size:14px;padding:4px;text-align:center;width:100%}.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right:0}.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right:0}.tox .tox-menu{background-color:#2b3b4e;border:1px solid rgba(255,255,255,.15);border-radius:6px;box-shadow:none;display:inline-block;overflow:hidden;vertical-align:top;z-index:1150}.tox .tox-menu.tox-collection.tox-collection--list{padding:0 4px}.tox .tox-menu.tox-collection.tox-collection--toolbar{padding:8px}.tox .tox-menu.tox-collection.tox-collection--grid{padding:8px}@media only screen and (min-width:768px){.tox .tox-menu .tox-collection__item-label{overflow-wrap:break-word;word-break:normal}.tox .tox-dialog__popups .tox-menu .tox-collection__item-label{word-break:break-all}}.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin:0}.tox .tox-menubar{background:repeating-linear-gradient(transparent 0 1px,transparent 1px 39px) center top 39px/100% calc(100% - 39px) no-repeat;background-color:#222f3e;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;grid-column:1/-1;grid-row:1;padding:0 11px 0 12px}.tox .tox-promotion+.tox-menubar{grid-column:1}.tox .tox-promotion{background:repeating-linear-gradient(transparent 0 1px,transparent 1px 39px) center top 39px/100% calc(100% - 39px) no-repeat;background-color:#222f3e;grid-column:2;grid-row:1;padding-inline-end:8px;padding-inline-start:4px;padding-top:5px}.tox .tox-promotion-link{align-items:unsafe center;background-color:#e8f1f8;border-radius:5px;color:#086be6;cursor:pointer;display:flex;font-size:14px;height:26.6px;padding:4px 8px;white-space:nowrap}.tox .tox-promotion-link:hover{background-color:#b4d7ff}.tox .tox-promotion-link:focus{background-color:#d9edf7}.tox .tox-mbtn{align-items:center;background:#222f3e;border:0;border-radius:3px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;justify-content:center;margin:5px 1px 6px 0;outline:0;padding:0 4px;text-transform:none;width:auto}.tox .tox-mbtn[disabled]{background-color:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-mbtn:focus:not(:disabled){background:#222f3e;border:0;box-shadow:none;color:#fff;position:relative;z-index:1}.tox .tox-mbtn:focus:not(:disabled)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-mbtn:focus:not(:disabled)::after{border:2px solid highlight}}.tox .tox-mbtn--active,.tox .tox-mbtn:not(:disabled).tox-mbtn--active:focus{background:#599fef;border:0;box-shadow:none;color:#fff}.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active){background:#2f4055;border:0;box-shadow:none;color:#fff}.tox .tox-mbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor:not-allowed}.tox .tox-mbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px;display:none}.tox .tox-notification{border-radius:6px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;display:grid;font-size:14px;font-weight:400;grid-template-columns:minmax(40px,1fr) auto minmax(40px,1fr);margin-left:auto;margin-right:auto;margin-top:4px;opacity:0;padding:4px;transition:transform .1s ease-in,opacity 150ms ease-in;width:-moz-max-content;width:max-content}.tox .tox-notification a{cursor:pointer;text-decoration:underline}.tox .tox-notification p{font-size:14px;font-weight:400}.tox .tox-notification:focus{border-color:#006ce7;box-shadow:0 0 0 1px #006ce7}.tox .tox-notification--in{opacity:1}.tox .tox-notification--success{background-color:#334840;border-color:#3c5440;color:#fff}.tox .tox-notification--success p{color:#fff}.tox .tox-notification--success a{color:#b5d199}.tox .tox-notification--success a:focus,.tox .tox-notification--success a:hover{color:#82b153;text-decoration:underline}.tox .tox-notification--success a:focus-visible{border-radius:1px;outline:2px solid #b5d199;outline-offset:2px}.tox .tox-notification--success a:active{color:#689041;text-decoration:underline}.tox .tox-notification--success svg{fill:#fff}.tox .tox-notification--error{background-color:#442632;border-color:#55212b;color:#fff}.tox .tox-notification--error p{color:#fff}.tox .tox-notification--error a{color:#e68080}.tox .tox-notification--error a:focus,.tox .tox-notification--error a:hover{color:#d42b2b;text-decoration:underline}.tox .tox-notification--error a:focus-visible{border-radius:1px;outline:2px solid #e68080;outline-offset:2px}.tox .tox-notification--error a:active{color:#a22;text-decoration:underline}.tox .tox-notification--error svg{fill:#fff}.tox .tox-notification--warn,.tox .tox-notification--warning{background-color:#222f3e;border-color:rgba(255,255,255,.15);color:#fff0b3}.tox .tox-notification--warn p,.tox .tox-notification--warning p{color:#fff0b3}.tox .tox-notification--warn a,.tox .tox-notification--warning a{color:#fc0}.tox .tox-notification--warn a:focus,.tox .tox-notification--warn a:hover,.tox .tox-notification--warning a:focus,.tox .tox-notification--warning a:hover{color:#997a00;text-decoration:underline}.tox .tox-notification--warn a:focus-visible,.tox .tox-notification--warning a:focus-visible{border-radius:1px;outline:2px solid #fc0;outline-offset:2px}.tox .tox-notification--warn a:active,.tox .tox-notification--warning a:active{color:#665200;text-decoration:underline}.tox .tox-notification--warn svg,.tox .tox-notification--warning svg{fill:#fff0b3}.tox .tox-notification--info{background-color:#254161;border-color:#264972;color:#fff}.tox .tox-notification--info p{color:#fff}.tox .tox-notification--info a{color:#83b7f3}.tox .tox-notification--info a:focus,.tox .tox-notification--info a:hover{color:#2681ea;text-decoration:underline}.tox .tox-notification--info a:focus-visible{border-radius:1px;outline:2px solid #83b7f3;outline-offset:2px}.tox .tox-notification--info a:active{color:#1368c9;text-decoration:underline}.tox .tox-notification--info svg{fill:#fff}.tox .tox-notification__body{align-self:center;color:#fff;font-size:14px;grid-column-end:3;grid-column-start:2;grid-row-end:2;grid-row-start:1;text-align:center;white-space:normal;word-break:break-all;word-break:break-word}.tox .tox-notification__body>*{margin:0}.tox .tox-notification__body>*+*{margin-top:1rem}.tox .tox-notification__icon{align-self:center;grid-column-end:2;grid-column-start:1;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification__icon svg{display:block}.tox .tox-notification__dismiss{align-self:start;grid-column-end:4;grid-column-start:3;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification .tox-progress-bar{grid-column-end:4;grid-column-start:1;grid-row-end:3;grid-row-start:2;justify-self:center}.tox .tox-notification-container-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-notification-container-dock-fadein{opacity:1;visibility:visible}.tox .tox-notification-container-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-notification-container-dock-transition.tox-notification-container-dock-fadein{transition-delay:0s}.tox .tox-pop{display:inline-block;position:relative}.tox .tox-pop--resizing{transition:width .1s ease}.tox .tox-pop--resizing .tox-toolbar,.tox .tox-pop--resizing .tox-toolbar__group{flex-wrap:nowrap}.tox .tox-pop--transition{transition:.15s ease;transition-property:left,right,top,bottom}.tox .tox-pop--transition::after,.tox .tox-pop--transition::before{transition:all .15s,visibility 0s,opacity 75ms ease 75ms}.tox .tox-pop__dialog{background-color:#222f3e;border:1px solid #161f29;border-radius:6px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);min-width:0;overflow:hidden}.tox .tox-pop__dialog>:not(.tox-toolbar){margin:4px 4px 4px 8px}.tox .tox-pop__dialog .tox-toolbar{background-color:transparent;margin-bottom:-1px}.tox .tox-pop::after,.tox .tox-pop::before{border-style:solid;content:'';display:block;height:0;opacity:1;position:absolute;width:0}@media (forced-colors:active){.tox .tox-pop::after,.tox .tox-pop::before{content:none}}.tox .tox-pop.tox-pop--inset::after,.tox .tox-pop.tox-pop--inset::before{opacity:0;transition:all 0s .15s,visibility 0s,opacity 75ms ease}.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{left:50%;top:100%}.tox .tox-pop.tox-pop--bottom::after{border-color:#222f3e transparent transparent transparent;border-width:8px;margin-left:-8px;margin-top:-1px}.tox .tox-pop.tox-pop--bottom::before{border-color:#161f29 transparent transparent transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{left:50%;top:0;transform:translateY(-100%)}.tox .tox-pop.tox-pop--top::after{border-color:transparent transparent #222f3e transparent;border-width:8px;margin-left:-8px;margin-top:1px}.tox .tox-pop.tox-pop--top::before{border-color:transparent transparent #161f29 transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{left:0;top:calc(50% - 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--left::after{border-color:transparent #222f3e transparent transparent;border-width:8px;margin-left:-15px}.tox .tox-pop.tox-pop--left::before{border-color:transparent #161f29 transparent transparent;border-width:10px;margin-left:-19px}.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{left:100%;top:calc(50% + 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--right::after{border-color:transparent transparent transparent #222f3e;border-width:8px;margin-left:-1px}.tox .tox-pop.tox-pop--right::before{border-color:transparent transparent transparent #161f29;border-width:10px;margin-left:-1px}.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left:20px}.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left:calc(100% - 20px)}.tox .tox-sidebar-wrap{display:flex;flex-direction:row;flex-grow:1;min-height:0}.tox .tox-sidebar{background-color:#222f3e;display:flex;flex-direction:row;justify-content:flex-end}.tox .tox-sidebar__slider{display:flex;overflow:hidden}.tox .tox-sidebar__pane-container{display:flex}.tox .tox-sidebar__pane{display:flex}.tox .tox-sidebar--sliding-closed{opacity:0}.tox .tox-sidebar--sliding-open{opacity:1}.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition:width .5s ease,opacity .5s ease}.tox .tox-selector{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;display:inline-block;height:10px;position:absolute;width:10px}.tox.tox-platform-touch .tox-selector{height:12px;width:12px}.tox .tox-slider{align-items:center;display:flex;flex:1;height:24px;justify-content:center;position:relative}.tox .tox-slider__rail{background-color:transparent;border:1px solid #161f29;border-radius:6px;height:10px;min-width:120px;width:100%}.tox .tox-slider__handle{background-color:#006ce7;border:2px solid #0054b4;border-radius:6px;box-shadow:none;height:24px;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:14px}.tox .tox-form__controls-h-stack>.tox-slider:not(:first-of-type){margin-inline-start:8px}.tox .tox-form__controls-h-stack>.tox-form__group+.tox-slider{margin-inline-start:32px}.tox .tox-form__controls-h-stack>.tox-slider+.tox-form__group{margin-inline-start:32px}.tox .tox-source-code{overflow:auto}.tox .tox-spinner{display:flex}.tox .tox-spinner>div{animation:tam-bouncing-dots 1.5s ease-in-out 0s infinite both;background-color:rgba(255,255,255,.5);border-radius:100%;height:8px;width:8px}.tox .tox-spinner>div:nth-child(1){animation-delay:-.32s}.tox .tox-spinner>div:nth-child(2){animation-delay:-.16s}@keyframes tam-bouncing-dots{0%,100%,80%{transform:scale(0)}40%{transform:scale(1)}}.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right:4px}.tox .tox-statusbar{align-items:center;background-color:#222f3e;border-top:1px solid rgba(255,255,255,.15);color:rgba(255,255,255,.75);display:flex;flex:0 0 auto;font-size:14px;font-weight:400;height:25px;overflow:hidden;padding:0 8px;position:relative;text-transform:none}.tox .tox-statusbar__path{display:flex;flex:1 1 auto;text-overflow:ellipsis;white-space:nowrap}.tox .tox-statusbar__right-container{display:flex;justify-content:flex-end;white-space:nowrap}.tox .tox-statusbar__help-text{text-align:center}.tox .tox-statusbar__text-container{align-items:flex-start;display:flex;flex:1 1 auto;height:16px;justify-content:space-between;overflow:hidden}@media only screen and (min-width:768px){.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__help-text,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__path,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__right-container{flex:0 0 calc(100% / 3)}}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-end{justify-content:flex-end}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-start{justify-content:flex-start}.tox .tox-statusbar__text-container.tox-statusbar__text-container--space-around{justify-content:space-around}.tox .tox-statusbar__path>*{display:inline;white-space:nowrap}.tox .tox-statusbar__wordcount{flex:0 0 auto;margin-left:1ch}@media only screen and (max-width:767px){.tox .tox-statusbar__text-container .tox-statusbar__help-text{display:none}.tox .tox-statusbar__text-container .tox-statusbar__help-text:only-child{display:block}}.tox .tox-statusbar a,.tox .tox-statusbar__path-item,.tox .tox-statusbar__wordcount{color:rgba(255,255,255,.75);position:relative;text-decoration:none}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){color:#fff;cursor:pointer}.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border:2px solid highlight}}.tox .tox-statusbar__branding svg{fill:rgba(255,255,255,.8);height:1em;margin-left:.3em;width:auto}@media (forced-colors:active){.tox .tox-statusbar__branding svg{fill:currentColor}}.tox .tox-statusbar__branding a{align-items:center;display:inline-flex}.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg,.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg{fill:#fff}.tox .tox-statusbar__resize-handle{align-items:flex-end;align-self:stretch;cursor:nwse-resize;display:flex;flex:0 0 auto;justify-content:flex-end;margin-bottom:3px;margin-left:4px;margin-right:calc(3px - 8px);margin-top:3px;padding-bottom:0;padding-left:0;padding-right:0;position:relative}.tox .tox-statusbar__resize-handle svg{display:block;fill:rgba(255,255,255,.5)}.tox .tox-statusbar__resize-handle:focus svg,.tox .tox-statusbar__resize-handle:hover svg{fill:#fff}.tox .tox-statusbar__resize-handle:focus-visible{background-color:transparent;border-radius:1px 1px 5px 1px;box-shadow:0 0 0 2px transparent}.tox .tox-statusbar__resize-handle:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar__resize-handle:focus-visible::after{border:2px solid highlight}}.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right:4px}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:2ch}.tox[dir=rtl] .tox-statusbar{flex-direction:row-reverse}.tox[dir=rtl] .tox-statusbar__path>*{margin-left:4px}.tox[dir=rtl] .tox-statusbar__branding svg{margin-left:0;margin-right:.3em}.tox .tox-throbber{z-index:1299}.tox .tox-throbber__busy-spinner{align-items:center;background-color:rgba(34,47,62,.6);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.tox .tox-tbtn{align-items:center;background:#222f3e;border:0;border-radius:3px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;justify-content:center;margin:6px 1px 5px 0;outline:0;padding:0;text-transform:none;width:34px}@media (forced-colors:active){.tox .tox-tbtn.tox-tbtn:hover,.tox .tox-tbtn:hover{outline:1px dashed currentColor}.tox .tox-tbtn.tox-tbtn--active,.tox .tox-tbtn.tox-tbtn--enabled,.tox .tox-tbtn.tox-tbtn--enabled:focus,.tox .tox-tbtn.tox-tbtn--enabled:hover,.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){outline:1px solid currentColor;position:relative}}.tox .tox-tbtn svg{display:block;fill:#fff}@media (forced-colors:active){.tox .tox-tbtn svg{fill:currentColor!important}.tox .tox-tbtn svg.tox-tbtn--enabled,.tox .tox-tbtn svg:focus:not(.tox-tbtn--disabled){fill:currentColor!important}.tox .tox-tbtn svg .tox-tbtn:disabled,.tox .tox-tbtn svg .tox-tbtn:disabled:hover,.tox .tox-tbtn svg.tox-tbtn--disabled,.tox .tox-tbtn svg.tox-tbtn--disabled:hover{filter:contrast(0)}}.tox .tox-tbtn.tox-tbtn-more{padding-left:5px;padding-right:5px;width:inherit}.tox .tox-tbtn:focus{background:#222f3e;border:0;box-shadow:none;position:relative;z-index:1}.tox .tox-tbtn:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn:focus::after{border:2px solid highlight}}.tox .tox-tbtn:hover{background:#2f4055;border:0;box-shadow:none;color:#fff}.tox .tox-tbtn:hover svg{fill:#fff}.tox .tox-tbtn:active{background:#599fef;border:0;box-shadow:none;color:#fff}.tox .tox-tbtn:active svg{fill:#fff}.tox .tox-tbtn--disabled .tox-tbtn--enabled svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{background:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--active,.tox .tox-tbtn--enabled,.tox .tox-tbtn--enabled:focus,.tox .tox-tbtn--enabled:hover{background:#599fef;border:0;box-shadow:none;color:#fff;position:relative}.tox .tox-tbtn--active>*,.tox .tox-tbtn--enabled:focus>*,.tox .tox-tbtn--enabled:hover>*,.tox .tox-tbtn--enabled>*{transform:none}.tox .tox-tbtn--active svg,.tox .tox-tbtn--enabled svg,.tox .tox-tbtn--enabled:focus svg,.tox .tox-tbtn--enabled:hover svg{fill:#fff}.tox .tox-tbtn--active.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:focus.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--enabled:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn--enabled:focus::after{border:2px solid highlight}}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){color:#fff}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg{fill:#fff}.tox .tox-tbtn:active>*{transform:none}.tox .tox-tbtn--md{height:42px;width:51px}.tox .tox-tbtn--lg{flex-direction:column;height:56px;width:68px}.tox .tox-tbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tbtn--labeled{padding:0 4px;width:unset}.tox .tox-tbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-number-input{background:#2f4055;border-radius:3px;display:flex;margin:6px 1px 5px 0;position:relative;width:auto}.tox .tox-number-input:focus{background:#2f4055}.tox .tox-number-input:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper{display:flex;pointer-events:none;position:relative;text-align:center}.tox .tox-number-input .tox-input-wrapper:focus{background-color:#2f4055;z-index:1}.tox .tox-number-input .tox-input-wrapper:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border:2px solid highlight}}.tox .tox-number-input input{border-radius:3px;color:#fff;font-size:14px;margin:2px 0;pointer-events:all;position:relative;width:60px}.tox .tox-number-input input:hover{background:#2f4055;color:#fff}.tox .tox-number-input input:focus{background-color:#2f4055}.tox .tox-number-input input:disabled{background:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-number-input button{color:#fff;height:28px;position:relative;text-align:center;width:24px}@media (forced-colors:active){.tox .tox-number-input button:active,.tox .tox-number-input button:focus,.tox .tox-number-input button:hover{outline:1px solid currentColor!important}}.tox .tox-number-input button svg{display:block;fill:#fff;margin:0 auto;transform:scale(.67)}@media (forced-colors:active){.tox .tox-number-input button svg,.tox .tox-number-input button svg:active,.tox .tox-number-input button svg:hover{fill:currentColor!important}.tox .tox-number-input button svg:disabled{filter:contrast(0)}}.tox .tox-number-input button:focus{background:#2f4055;z-index:1}.tox .tox-number-input button:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input button:focus::after{border:2px solid highlight}}.tox .tox-number-input button:hover{background:#2f4055;border:0;box-shadow:none;color:#fff}.tox .tox-number-input button:hover svg{fill:#fff}.tox .tox-number-input button:active{background:#599fef;border:0;box-shadow:none;color:#fff}.tox .tox-number-input button:active svg{fill:#fff}.tox .tox-number-input button:disabled{background:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-number-input button:disabled svg{fill:rgba(255,255,255,.5)}.tox .tox-number-input button.minus{border-radius:3px 0 0 3px}.tox .tox-number-input button.plus{border-radius:0 3px 3px 0}.tox .tox-number-input:focus:not(:active)>.tox-input-wrapper,.tox .tox-number-input:focus:not(:active)>button{background:#2f4055}.tox .tox-tbtn--select{margin:6px 1px 5px 0;padding:0 4px;width:auto}.tox .tox-tbtn__select-label{cursor:default;font-weight:400;height:initial;margin:0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-tbtn__select-chevron svg{fill:rgba(255,255,255,.5)}@media (forced-colors:active){.tox .tox-tbtn__select-chevron svg{fill:currentColor}}.tox .tox-tbtn--bespoke{background:#2f4055}.tox .tox-tbtn--bespoke:focus{background:#2f4055}.tox .tox-tbtn--bespoke+.tox-tbtn--bespoke{margin-inline-start:4px}.tox .tox-tbtn--bespoke .tox-tbtn__select-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:7em}.tox .tox-tbtn--disabled .tox-tbtn__select-label,.tox .tox-tbtn--select:disabled .tox-tbtn__select-label{cursor:not-allowed}.tox .tox-split-button{border:0;border-radius:3px;box-sizing:border-box;display:flex;margin:6px 1px 5px 0}.tox .tox-split-button:hover{box-shadow:0 0 0 1px #2f4055 inset}.tox .tox-split-button:focus{background:#222f3e;box-shadow:none;color:#fff;position:relative;z-index:1}.tox .tox-split-button:focus::after{pointer-events:none;border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-split-button:focus::after{border:2px solid highlight}}.tox .tox-split-button>*{border-radius:0}.tox .tox-split-button>:nth-child(1){border-bottom-left-radius:3px;border-top-left-radius:3px}.tox .tox-split-button>:nth-child(2){border-bottom-right-radius:3px;border-top-right-radius:3px}.tox .tox-split-button__chevron{width:16px}.tox .tox-split-button__chevron svg{fill:rgba(255,255,255,.5)}@media (forced-colors:active){.tox .tox-split-button__chevron svg{fill:currentColor}}.tox .tox-split-button .tox-tbtn{margin:0}.tox .tox-split-button:focus .tox-tbtn{background-color:transparent}.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{background:#222f3e;box-shadow:none;color:rgba(255,255,255,.5)}.tox.tox-platform-touch .tox-split-button .tox-tbtn--select{padding:0 0}.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child{width:30px}.tox.tox-platform-touch .tox-split-button__chevron{width:20px}.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color,.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color{opacity:.6}.tox .tox-toolbar-overlord{background-color:#222f3e}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background-attachment:local;background-color:#222f3e;background-image:repeating-linear-gradient(rgba(255,255,255,.15) 0 1px,transparent 1px 39px);background-position:center top 40px;background-repeat:no-repeat;background-size:calc(100% - 11px * 2) calc(100% - 41px);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 0;transform:perspective(1px)}.tox .tox-toolbar-overlord>.tox-toolbar,.tox .tox-toolbar-overlord>.tox-toolbar__overflow,.tox .tox-toolbar-overlord>.tox-toolbar__primary{background-position:center top 0;background-size:calc(100% - 11px * 2) calc(100% - 0px)}.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height:0;opacity:0;padding-bottom:0;padding-top:0;visibility:hidden}.tox .tox-toolbar__overflow--growing{transition:height .3s ease,opacity .2s linear .1s}.tox .tox-toolbar__overflow--shrinking{transition:opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s}.tox .tox-anchorbar,.tox .tox-toolbar-overlord{grid-column:1/-1}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{border-top:1px solid transparent;margin-top:-1px;padding-bottom:1px;padding-top:1px}@media (forced-colors:active){.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{outline:1px solid currentColor}}.tox .tox-toolbar--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-pop .tox-toolbar{border-width:0}.tox .tox-toolbar--no-divider{background-image:none}.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,.tox .tox-toolbar-overlord .tox-toolbar__primary{background-position:center top 39px}.tox .tox-editor-header>.tox-toolbar--scrolling,.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child{background-image:none}.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color:#222f3e;background-position:center top 43px;background-size:calc(100% - 8px * 2) calc(100% - 51px);border:none;border-radius:6px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);overscroll-behavior:none;padding:4px 0}@media (forced-colors:active){.tox.tox-tinymce-aux .tox-toolbar__overflow{border:solid}}.tox-pop .tox-pop__dialog .tox-toolbar{background-position:center top 43px;background-size:calc(100% - 11px * 2) calc(100% - 51px);padding:4px 0}.tox .tox-toolbar__group{align-items:center;display:flex;flex-wrap:wrap;margin:0 0;padding:0 11px 0 12px}.tox .tox-toolbar__group--pull-right{margin-left:auto}.tox .tox-toolbar--scrolling .tox-toolbar__group{flex-shrink:0;flex-wrap:nowrap}.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right:1px solid transparent}.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left:1px solid transparent}.tox .tox-tooltip{display:inline-block;max-width:15em;padding:8px;pointer-events:none;position:relative;width:-moz-max-content;width:max-content;z-index:1150}.tox .tox-tooltip__body{background-color:#324053;border-radius:6px;box-shadow:none;color:#fff;font-size:12px;font-style:normal;font-weight:600;overflow-wrap:break-word;padding:4px 6px;text-transform:none}@media (forced-colors:active){.tox .tox-tooltip__body{outline:outset 1px}}.tox .tox-tooltip__arrow{position:absolute}.tox .tox-tooltip--down .tox-tooltip__arrow{border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #324053;bottom:0;left:50%;position:absolute;transform:translateX(-50%)}.tox .tox-tooltip--up .tox-tooltip__arrow{border-bottom:8px solid #324053;border-left:8px solid transparent;border-right:8px solid transparent;left:50%;position:absolute;top:0;transform:translateX(-50%)}.tox .tox-tooltip--right .tox-tooltip__arrow{border-bottom:8px solid transparent;border-left:8px solid #324053;border-top:8px solid transparent;position:absolute;right:0;top:50%;transform:translateY(-50%)}.tox .tox-tooltip--left .tox-tooltip__arrow{border-bottom:8px solid transparent;border-right:8px solid #324053;border-top:8px solid transparent;left:0;position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-tree{display:flex;flex-direction:column}.tox .tox-tree .tox-trbtn{align-items:center;background:0 0;border:0;border-radius:4px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;margin-bottom:4px;margin-top:4px;outline:0;overflow:hidden;padding:0;padding-left:8px;text-transform:none}.tox .tox-tree .tox-trbtn .tox-tree__label{cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tree .tox-trbtn svg{display:block;fill:#fff}.tox .tox-tree .tox-trbtn:focus{background:#2f4055;border:0;box-shadow:none}.tox .tox-tree .tox-trbtn:hover{background:#2f4055;border:0;box-shadow:none;color:#fff}.tox .tox-tree .tox-trbtn:hover svg{fill:#fff}.tox .tox-tree .tox-trbtn:active{background:#599fef;border:0;box-shadow:none;color:#fff}.tox .tox-tree .tox-trbtn:active svg{fill:#fff}.tox .tox-tree .tox-trbtn--disabled,.tox .tox-tree .tox-trbtn--disabled:hover,.tox .tox-tree .tox-trbtn:disabled,.tox .tox-tree .tox-trbtn:disabled:hover{background:0 0;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-tree .tox-trbtn--disabled svg,.tox .tox-tree .tox-trbtn--disabled:hover svg,.tox .tox-tree .tox-trbtn:disabled svg,.tox .tox-tree .tox-trbtn:disabled:hover svg{fill:rgba(255,255,255,.5)}.tox .tox-tree .tox-trbtn--enabled,.tox .tox-tree .tox-trbtn--enabled:hover{background:#599fef;border:0;box-shadow:none;color:#fff}.tox .tox-tree .tox-trbtn--enabled:hover>*,.tox .tox-tree .tox-trbtn--enabled>*{transform:none}.tox .tox-tree .tox-trbtn--enabled svg,.tox .tox-tree .tox-trbtn--enabled:hover svg{fill:#fff}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled){color:#fff}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg{fill:#fff}.tox .tox-tree .tox-trbtn:active>*{transform:none}.tox .tox-tree .tox-trbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tree .tox-trbtn--labeled{padding:0 4px;width:unset}.tox .tox-tree .tox-trbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-tree .tox-tree--directory{display:flex;flex-direction:column}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label{font-weight:700}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg{fill:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg{fill:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron{margin-right:6px}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--shrinking) .tox-chevron{transition:transform .5s ease-in-out}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--open) .tox-chevron{transform:rotate(90deg)}.tox .tox-tree .tox-tree--leaf__label{font-weight:400}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg{fill:#fff}.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg{fill:#fff}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#fff}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#fff}.tox .tox-tree .tox-tree--directory__children{overflow:hidden;padding-left:16px}.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking{transition:height .5s ease-in-out}.tox .tox-tree .tox-trbtn.tox-tree--leaf__label{display:flex;justify-content:space-between}.tox .tox-revisionhistory__pane{padding:0!important}.tox .tox-revisionhistory__container{display:flex;flex-direction:column;height:100%}.tox .tox-revisionhistory{background-color:#2b3b4e;border-radius:4px;border-top:1px solid #161f29;display:flex;flex:1;height:100%;margin-top:8px;overflow-x:auto;overflow-y:hidden;position:relative;width:100%}.tox .tox-revisionhistory--align-right{margin-left:auto}.tox .tox-revisionhistory__iframe{flex:1}.tox .tox-revisionhistory__sidebar{border-left:1px solid #161f29;height:100%;max-width:360px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__sidebar-title{border-bottom:1px solid #161f29;color:#fff;font-size:20px;font-weight:400;height:60px;min-width:192px;padding:16px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions{flex-direction:column;max-height:calc(100% - 60px);min-width:192px;overflow-y:auto;padding:8px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus{height:100%;position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0;border-radius:6px;bottom:1px;left:1px;right:1px;top:1px}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card{border:1px solid #161f29;border-radius:6px;color:#fff;cursor:pointer;font-size:14px;margin-bottom:8px;padding:8px;text-overflow:ellipsis;text-wrap:nowrap;width:100%}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:hover{background-color:#2f4055;box-shadow:none;color:#fff}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus{position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border-radius:6px!important;border-radius:3px;bottom:0;box-shadow:0 0 0 2px #fff;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card.tox-revisionhistory__card--selected{background-color:#599fef;box-shadow:none;color:#fff}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__norevision{color:rgba(255,255,255,.5);font-size:16px;line-height:24px;padding:5px 5.5px}.tox .tox-view-wrap,.tox .tox-view-wrap__slot-container{background-color:#222f3e;display:flex;flex:1;flex-direction:column;height:100%}.tox .tox-view{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-view__header{align-items:center;display:flex;font-size:16px;justify-content:space-between;padding:10px 10px 2px 10px;position:relative}.tox .tox-view__label{color:#fff;font-weight:700;line-height:24px;padding:4px 16px;text-align:center;white-space:nowrap}.tox .tox-view__label--normal{font-size:16px}.tox .tox-view__label--large{font-size:20px}.tox .tox-view--mobile.tox-view__header,.tox .tox-view--mobile.tox-view__toolbar{padding:8px}.tox .tox-view--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-view__toolbar{display:flex;flex-direction:row;gap:8px;justify-content:space-between;overflow-x:auto;padding:10px 10px 2px 10px}.tox .tox-view__toolbar__group{display:flex;flex-direction:row;gap:12px}.tox .tox-view__header-end,.tox .tox-view__header-start{display:flex}.tox .tox-view__pane{height:100%;padding:8px;position:relative;width:100%}.tox .tox-view__pane_panel{border:1px solid #161f29;border-radius:6px}.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end>*,.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start>*{margin-left:8px}.tox[dir=rtl] .tox-view__header .tox-view__header-end>*,.tox[dir=rtl] .tox-view__header .tox-view__header-start>*{margin-right:8px}.tox .tox-well{border:1px solid #161f29;border-radius:6px;padding:8px;width:100%}.tox .tox-well>:first-child{margin-top:0}.tox .tox-well>:last-child{margin-bottom:0}.tox .tox-well>:only-child{margin:0}.tox .tox-custom-editor{border:1px solid #161f29;border-radius:6px;display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-dialog-loading::before{background-color:rgba(0,0,0,.5);content:\"\";height:100%;position:absolute;width:100%;z-index:1000}.tox .tox-tab{cursor:pointer}.tox .tox-dialog__content-js{display:flex;flex:1}.tox .tox-dialog__body-content .tox-collection{display:flex;flex:1}.tox.tox-tinymce-aux .tox-toolbar__overflow{box-shadow:0 0 0 1px rgba(255,255,255,.15)}") //# sourceMappingURL=skin.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/oxide-dark/skin.shadowdom.js ================================================ tinymce.Resource.add('ui/dark/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}") //# sourceMappingURL=skin.shadowdom.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5/content.inline.js ================================================ tinymce.Resource.add('ui/tinymce-5/content.inline.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.1)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.1);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}") //# sourceMappingURL=content.inline.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5/content.js ================================================ tinymce.Resource.add('ui/tinymce-5/content.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.1)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.1);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}body{font-family:sans-serif}table{border-collapse:collapse}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5/skin.js ================================================ tinymce.Resource.add('ui/tinymce-5/skin.css', ".tox{box-shadow:none;box-sizing:content-box;color:#222f3e;cursor:auto;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-style:normal;font-weight:400;line-height:normal;-webkit-tap-highlight-color:transparent;text-decoration:none;text-shadow:none;text-transform:none;vertical-align:initial;white-space:normal}.tox :not(svg):not(rect){box-sizing:inherit;color:inherit;cursor:inherit;direction:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:inherit;-webkit-tap-highlight-color:inherit;text-align:inherit;text-decoration:inherit;text-shadow:inherit;text-transform:inherit;vertical-align:inherit;white-space:inherit}.tox :not(svg):not(rect){background:0 0;border:0;box-shadow:none;float:none;height:auto;margin:0;max-width:none;outline:0;padding:0;position:static;width:auto}.tox:not([dir=rtl]){direction:ltr;text-align:left}.tox[dir=rtl]{direction:rtl;text-align:right}.tox-tinymce{border:1px solid #ccc;border-radius:0;box-shadow:none;box-sizing:border-box;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;overflow:hidden;position:relative;visibility:inherit!important}.tox.tox-tinymce-inline{border:none;box-shadow:none;overflow:initial}.tox.tox-tinymce-inline .tox-editor-container{overflow:initial}.tox.tox-tinymce-inline .tox-editor-header{background-color:#fff;border:1px solid #ccc;border-radius:0;box-shadow:none;overflow:hidden}.tox-tinymce-aux{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;z-index:1300}.tox-tinymce :focus,.tox-tinymce-aux :focus{outline:0}button::-moz-focus-inner{border:0}.tox[dir=rtl] .tox-icon--flip svg{transform:rotateY(180deg)}.tox .accessibility-issue__header{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description{align-items:stretch;border-radius:3px;display:flex;justify-content:space-between}.tox .accessibility-issue__description>div{padding-bottom:4px}.tox .accessibility-issue__description>div>div{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description>div>div .tox-icon svg{display:block}.tox .accessibility-issue__repair{margin-top:16px}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description{background-color:rgba(30,113,170,.1);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2{color:#207ab7}.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg{fill:#207ab7}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon{background-color:#207ab7;color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover{background-color:#1c6ca1}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active{background-color:#185d8c}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description{background-color:rgba(255,165,0,.08);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2{color:#8f5d00}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg{fill:#8f5d00}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon{background-color:#ffe89d;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover{background-color:#f2d574;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active{background-color:#e8c657;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description{background-color:rgba(204,0,0,.1);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2{color:#c00}.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg{fill:#c00}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon{background-color:#f2bfbf;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover{background-color:#e9a4a4;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active{background-color:#ee9494;color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description{background-color:rgba(120,171,70,.1);color:#222f3e}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description>:last-child{display:none}.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2{color:#527530}.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg{fill:#527530}.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2{font-size:14px;margin-top:0}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-left:4px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-left:auto}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description{padding:4px 4px 4px 8px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-right:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-right:auto}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description{padding:4px 8px 4px 4px}.tox .mce-codemirror{background:#fff;bottom:0;font-size:13px;left:0;position:absolute;right:0;top:0;z-index:1}.tox .mce-codemirror.tox-inline-codemirror{margin:8px;position:absolute}.tox .tox-advtemplate .tox-form__grid{flex:1}.tox .tox-advtemplate .tox-form__grid>div:first-child{display:flex;flex-direction:column;width:30%}.tox .tox-advtemplate .tox-form__grid>div:first-child>div:nth-child(2){flex-basis:0;flex-grow:1;overflow:auto}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid>div:first-child{width:100%}}.tox .tox-advtemplate iframe{border-color:#ccc;border-radius:0;border-style:solid;border-width:1px;margin:0 10px}.tox .tox-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bottom-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bar{display:flex;flex:0 0 auto}.tox .tox-button{background-color:#207ab7;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#207ab7;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;line-height:24px;margin:0;outline:0;padding:4px 16px;position:relative;text-align:center;text-decoration:none;text-transform:none;white-space:nowrap}.tox .tox-button::before{border-radius:3px;bottom:-1px;box-shadow:inset 0 0 0 1px #fff,0 0 0 2px #207ab7;content:'';left:-1px;opacity:0;pointer-events:none;position:absolute;right:-1px;top:-1px}.tox .tox-button[disabled]{background-color:#207ab7;background-image:none;border-color:#207ab7;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button:focus:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:focus:not(:disabled)::before{opacity:1}.tox .tox-button:hover:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:active:not(:disabled){background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled{background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled[disabled]{background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button.tox-button--enabled:focus:not(:disabled){background-color:#154f76;background-image:none;border-color:#154f76;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:hover:not(:disabled){background-color:#154f76;background-image:none;border-color:#154f76;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:active:not(:disabled){background-color:#114060;background-image:none;border-color:#114060;box-shadow:none;color:#fff}.tox .tox-button--icon-and-text,.tox .tox-button.tox-button--icon-and-text,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text{display:flex;padding:5px 4px}.tox .tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg{display:block;fill:currentColor}.tox .tox-button--secondary{background-color:#f0f0f0;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#f0f0f0;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;color:#222f3e;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;outline:0;padding:4px 16px;text-decoration:none;text-transform:none}.tox .tox-button--secondary[disabled]{background-color:#f0f0f0;background-image:none;border-color:#f0f0f0;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-button--secondary:focus:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--secondary:hover:not(:disabled){background-color:#e3e3e3;background-image:none;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--secondary:active:not(:disabled){background-color:#d6d6d6;background-image:none;border-color:#d6d6d6;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled{background-color:#b1ccdf;background-image:none;border-color:#b1ccdf;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled[disabled]{background-color:#b1ccdf;background-image:none;border-color:#b1ccdf;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled){background-color:#9fc1d7;background-image:none;border-color:#9fc1d7;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled){background-color:#9fc1d7;background-image:none;border-color:#9fc1d7;box-shadow:none;color:#222f3e}.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled){background-color:#8db5d0;background-image:none;border-color:#8db5d0;box-shadow:none;color:#222f3e}.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding:4px}.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display:block;fill:currentColor}.tox .tox-button-link{background:0;border:none;box-sizing:border-box;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;white-space:nowrap}.tox .tox-button-link--sm{font-size:14px}.tox .tox-button--naked{background-color:transparent;border-color:transparent;box-shadow:unset;color:#222f3e}.tox .tox-button--naked[disabled]{background-color:#f0f0f0;border-color:#f0f0f0;box-shadow:none;color:rgba(34,47,62,.5)}.tox .tox-button--naked:hover:not(:disabled){background-color:#e3e3e3;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--naked:focus:not(:disabled){background-color:#e3e3e3;border-color:#e3e3e3;box-shadow:none;color:#222f3e}.tox .tox-button--naked:active:not(:disabled){background-color:#d6d6d6;border-color:#d6d6d6;box-shadow:none;color:#222f3e}.tox .tox-button--naked .tox-icon svg{fill:currentColor}.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color:#222f3e}.tox .tox-checkbox{align-items:center;border-radius:3px;cursor:pointer;display:flex;height:36px;min-width:36px}.tox .tox-checkbox__input{height:1px;overflow:hidden;position:absolute;top:auto;width:1px}.tox .tox-checkbox__icons{align-items:center;border-radius:3px;box-shadow:0 0 0 2px transparent;box-sizing:content-box;display:flex;height:24px;justify-content:center;padding:calc(4px - 1px);width:24px}.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:block;fill:rgba(34,47,62,.3)}@media (forced-colors:active){.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:currentColor!important}}.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:none;fill:#207ab7}.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display:none;fill:#207ab7}.tox .tox-checkbox--disabled{color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg{fill:rgba(34,47,62,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:rgba(34,47,62,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:rgba(34,47,62,.5)}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display:block}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:block}.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:3px;box-shadow:inset 0 0 0 1px #207ab7;padding:calc(4px - 1px)}.tox:not([dir=rtl]) .tox-checkbox__label{margin-left:4px}.tox:not([dir=rtl]) .tox-checkbox__input{left:-10000px}.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left:4px}.tox[dir=rtl] .tox-checkbox__label{margin-right:4px}.tox[dir=rtl] .tox-checkbox__input{right:-10000px}.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right:4px}.tox .tox-collection--toolbar .tox-collection__group{display:flex;padding:0}.tox .tox-collection--grid .tox-collection__group{display:flex;flex-wrap:wrap;max-height:208px;overflow-x:hidden;overflow-y:auto;padding:0}.tox .tox-collection--list .tox-collection__group{border-bottom-width:0;border-color:#ccc;border-left-width:0;border-right-width:0;border-style:solid;border-top-width:1px;padding:4px 0}.tox .tox-collection--list .tox-collection__group:first-child{border-top-width:0}.tox .tox-collection__group-heading{background-color:#e6e6e6;color:rgba(34,47,62,.7);cursor:default;font-size:12px;font-style:normal;font-weight:400;margin-bottom:4px;margin-top:-4px;padding:4px 8px;text-transform:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection__item{align-items:center;border-radius:3px;color:#222f3e;display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection--list .tox-collection__item{padding:4px 8px}.tox .tox-collection--toolbar .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--grid .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--list .tox-collection__item--enabled{background-color:#fff;color:#222f3e}.tox .tox-collection--list .tox-collection__item--active{background-color:#dee0e2}.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{background-color:#c8cbcf;color:#222f3e}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active{background-color:#fff;position:relative}.tox .tox-collection--toolbar .tox-collection__item--active:hover{background-color:#dee0e2;color:#222f3e}.tox .tox-collection--toolbar .tox-collection__item--active:focus{background-color:#dee0e2;color:#222f3e}.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border:2px solid highlight}}.tox .tox-collection--grid .tox-collection__item--enabled{background-color:#c8cbcf;color:#222f3e}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:#dee0e2;color:#222f3e;position:relative;z-index:1}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent inset;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border:2px solid highlight}}.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#222f3e}@media (forced-colors:active){.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){border:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#222f3e}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled):hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection__item-checkmark,.tox .tox-collection__item-icon{align-items:center;display:flex;height:24px;justify-content:center;width:24px}.tox .tox-collection__item-checkmark svg,.tox .tox-collection__item-icon svg{fill:currentColor}.tox .tox-collection--toolbar-lg .tox-collection__item-icon{height:48px;width:48px}.tox .tox-collection__item-label{color:currentColor;display:inline-block;flex:1;font-size:14px;font-style:normal;font-weight:400;line-height:24px;max-width:100%;text-transform:none;word-break:break-all}.tox .tox-collection__item-accessory{color:currentColor;display:inline-block;font-size:14px;height:24px;line-height:24px;text-transform:none}.tox .tox-collection__item-caret{align-items:center;display:flex;min-height:24px}.tox .tox-collection__item-caret::after{content:'';font-size:0;min-height:inherit}.tox .tox-collection__item-caret svg{fill:currentColor}.tox .tox-collection__item--state-disabled{background-color:transparent;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg{fill:rgba(34,47,62,.5)}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display:none}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory+.tox-collection__item-checkmark{display:none}.tox .tox-collection--horizontal{background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:nowrap;margin-bottom:0;overflow-x:auto;padding:0}.tox .tox-collection--horizontal .tox-collection__group{align-items:center;display:flex;flex-wrap:nowrap;margin:0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item{height:34px;margin:3px 0 2px 0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item-label{white-space:nowrap}.tox .tox-collection--horizontal .tox-collection__item-caret{margin-left:4px}.tox .tox-collection__item-container{display:flex}.tox .tox-collection__item-container--row{align-items:center;flex:1 1 auto;flex-direction:row}.tox .tox-collection__item-container--row.tox-collection__item-container--align-left{margin-right:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--align-right{justify-content:flex-end;margin-left:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top{align-items:flex-start;margin-bottom:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle{align-items:center}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom{align-items:flex-end;margin-top:auto}.tox .tox-collection__item-container--column{align-self:center;flex:1 1 auto;flex-direction:column}.tox .tox-collection__item-container--column.tox-collection__item-container--align-left{align-items:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--align-right{align-items:flex-end}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top{align-self:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle{align-self:center}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom{align-self:flex-end}.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-right:1px solid #ccc}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left:8px}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-left:4px}.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left:16px;text-align:right}.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret{margin-left:16px}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-left:1px solid #ccc}.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right:8px}.tox[dir=rtl] .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-right:4px}.tox[dir=rtl] .tox-collection__item-accessory{margin-right:16px;text-align:left}.tox[dir=rtl] .tox-collection .tox-collection__item-caret{margin-right:16px;transform:rotateY(180deg)}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret{margin-right:4px}@media (forced-colors:active){.tox .tox-hue-slider,.tox .tox-rgb-form .tox-rgba-preview{background-color:currentColor!important;border:1px solid highlight!important;forced-color-adjust:none}}.tox .tox-color-picker-container{display:flex;flex-direction:row;height:225px;margin:0}.tox .tox-sv-palette{box-sizing:border-box;display:flex;height:100%}.tox .tox-sv-palette-spectrum{height:100%}.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width:225px}.tox .tox-sv-palette-thumb{background:0 0;border:1px solid #000;border-radius:50%;box-sizing:content-box;height:12px;position:absolute;width:12px}.tox .tox-sv-palette-inner-thumb{border:1px solid #fff;border-radius:50%;height:10px;position:absolute;width:10px}.tox .tox-hue-slider{box-sizing:border-box;height:100%;width:25px}.tox .tox-hue-slider-spectrum{background:linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);height:100%;width:100%}.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width:20px}.tox .tox-hue-slider-spectrum:focus,.tox .tox-sv-palette-spectrum:focus{outline:#08f solid}.tox .tox-hue-slider-thumb{background:#fff;border:1px solid #000;box-sizing:content-box;height:4px;width:100%}.tox .tox-rgb-form{display:flex;flex-direction:column;justify-content:space-between}.tox .tox-rgb-form div{align-items:center;display:flex;justify-content:space-between;margin-bottom:5px;width:inherit}.tox .tox-rgb-form input{width:6em}.tox .tox-rgb-form input.tox-invalid{border:1px solid red!important}.tox .tox-rgb-form .tox-rgba-preview{border:1px solid #000;flex-grow:2;margin-bottom:0}.tox:not([dir=rtl]) .tox-sv-palette{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left:-1px}.tox:not([dir=rtl]) .tox-rgb-form label{margin-right:.5em}.tox[dir=rtl] .tox-sv-palette{margin-left:15px}.tox[dir=rtl] .tox-hue-slider{margin-left:15px}.tox[dir=rtl] .tox-hue-slider-thumb{margin-right:-1px}.tox[dir=rtl] .tox-rgb-form label{margin-left:.5em}.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin:2px 0 3px 4px}.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{border:0;margin:-4px 0}.tox .tox-swatches__row{display:flex}@media (forced-colors:active){.tox .tox-swatches__row{forced-color-adjust:none}}.tox .tox-swatch{height:30px;transition:transform .15s,box-shadow .15s;width:30px}.tox .tox-swatch:focus,.tox .tox-swatch:hover{box-shadow:0 0 0 1px rgba(127,127,127,.3) inset;transform:scale(.8)}.tox .tox-swatch--remove{align-items:center;display:flex;justify-content:center}.tox .tox-swatch--remove svg path{stroke:#e74c3c}.tox .tox-swatches__picker-btn{align-items:center;background-color:transparent;border:0;cursor:pointer;display:flex;height:30px;justify-content:center;outline:0;padding:0;width:30px}.tox .tox-swatches__picker-btn svg{fill:#222f3e;height:24px;width:24px}.tox .tox-swatches__picker-btn:hover{background:#dee0e2}.tox div.tox-swatch:not(.tox-swatch--remove) svg{display:none;fill:#222f3e;height:24px;margin:calc((30px - 24px)/ 2) calc((30px - 24px)/ 2);width:24px}.tox div.tox-swatch:not(.tox-swatch--remove) svg path{fill:#fff;paint-order:stroke;stroke:#222f3e;stroke-width:2px}.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg{display:block}.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left:auto}.tox[dir=rtl] .tox-swatches__picker-btn{margin-right:auto}.tox .tox-comment-thread{background:#fff;position:relative}.tox .tox-comment-thread>:not(:first-child){margin-top:8px}.tox .tox-comment{background:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 4px 8px 0 rgba(34,47,62,.1);padding:8px 8px 16px 8px;position:relative}.tox .tox-comment__header{align-items:center;color:#222f3e;display:flex;justify-content:space-between}.tox .tox-comment__date{color:#222f3e;font-size:12px;line-height:18px}.tox .tox-comment__body{color:#222f3e;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;margin-top:8px;position:relative;text-transform:initial}.tox .tox-comment__body textarea{resize:none;white-space:normal;width:100%}.tox .tox-comment__expander{padding-top:8px}.tox .tox-comment__expander p{color:rgba(34,47,62,.7);font-size:14px;font-style:normal}.tox .tox-comment__body p{margin:0}.tox .tox-comment__buttonspacing{padding-top:16px;text-align:center}.tox .tox-comment-thread__overlay::after{background:#fff;bottom:0;content:\"\";display:flex;left:0;opacity:.9;position:absolute;right:0;top:0;z-index:5}.tox .tox-comment__reply{display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;margin-top:8px}.tox .tox-comment__reply>:first-child{margin-bottom:8px;width:100%}.tox .tox-comment__edit{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:16px}.tox .tox-comment__gradient::after{background:linear-gradient(rgba(255,255,255,0),#fff);bottom:0;content:\"\";display:block;height:5em;margin-top:-40px;position:absolute;width:100%}.tox .tox-comment__overlay{background:#fff;bottom:0;display:flex;flex-direction:column;flex-grow:1;left:0;opacity:.9;position:absolute;right:0;text-align:center;top:0;z-index:5}.tox .tox-comment__loading-text{align-items:center;color:#222f3e;display:flex;flex-direction:column;position:relative}.tox .tox-comment__loading-text>div{padding-bottom:16px}.tox .tox-comment__overlaytext{bottom:0;flex-direction:column;font-size:14px;left:0;padding:1em;position:absolute;right:0;top:0;z-index:10}.tox .tox-comment__overlaytext p{background-color:#fff;box-shadow:0 0 8px 8px #fff;color:#222f3e;text-align:center}.tox .tox-comment__overlaytext div:nth-of-type(2){font-size:.8em}.tox .tox-comment__busy-spinner{align-items:center;background-color:#fff;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:20}.tox .tox-comment__scroll{display:flex;flex-direction:column;flex-shrink:1;overflow:auto}.tox .tox-conversations{margin:8px}.tox:not([dir=rtl]) .tox-comment__edit{margin-left:8px}.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left:8px}.tox[dir=rtl] .tox-comment__edit{margin-right:8px}.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right:8px}.tox .tox-user{align-items:center;display:flex}.tox .tox-user__avatar svg{fill:rgba(34,47,62,.7)}.tox .tox-user__avatar img{border-radius:50%;height:36px;object-fit:cover;vertical-align:middle;width:36px}.tox .tox-user__name{color:#222f3e;font-size:14px;font-style:normal;font-weight:700;line-height:18px;text-transform:none}.tox:not([dir=rtl]) .tox-user__avatar img,.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right:8px}.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left:8px}.tox[dir=rtl] .tox-user__avatar img,.tox[dir=rtl] .tox-user__avatar svg{margin-left:8px}.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right:8px}.tox .tox-dialog-wrap{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1100}.tox .tox-dialog-wrap__backdrop{background-color:rgba(255,255,255,.75);bottom:0;left:0;position:absolute;right:0;top:0;z-index:1}.tox .tox-dialog-wrap__backdrop--opaque{background-color:#fff}.tox .tox-dialog{background-color:#fff;border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;box-shadow:0 16px 16px -10px rgba(34,47,62,.15),0 0 40px 1px rgba(34,47,62,.15);display:flex;flex-direction:column;max-height:100%;max-width:480px;overflow:hidden;position:relative;width:95vw;z-index:2}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog{align-self:flex-start;margin:8px auto;max-height:calc(100vh - 8px * 2);width:calc(100vw - 16px)}}.tox .tox-dialog-inline{z-index:1100}.tox .tox-dialog__header{align-items:center;background-color:#fff;border-bottom:none;color:#222f3e;display:flex;font-size:16px;justify-content:space-between;padding:8px 16px 0 16px;position:relative}.tox .tox-dialog__header .tox-button{z-index:1}.tox .tox-dialog__draghandle{cursor:grab;height:100%;left:0;position:absolute;top:0;width:100%}.tox .tox-dialog__draghandle:active{cursor:grabbing}.tox .tox-dialog__dismiss{margin-left:auto}.tox .tox-dialog__title{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:20px;font-style:normal;font-weight:400;line-height:1.3;margin:0;text-transform:none}.tox .tox-dialog__body{color:#222f3e;display:flex;flex:1;font-size:16px;font-style:normal;font-weight:400;line-height:1.3;min-width:0;text-align:left;text-transform:none}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body{flex-direction:column}}.tox .tox-dialog__body-nav{align-items:flex-start;display:flex;flex-direction:column;flex-shrink:0;padding:16px 16px}@media only screen and (min-width:768px){.tox .tox-dialog__body-nav{max-width:11em}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body-nav{flex-direction:row;-webkit-overflow-scrolling:touch;overflow-x:auto;padding-bottom:0}}.tox .tox-dialog__body-nav-item{border-bottom:2px solid transparent;color:rgba(34,47,62,.7);display:inline-block;flex-shrink:0;font-size:14px;line-height:1.3;margin-bottom:8px;max-width:13em;text-decoration:none}.tox .tox-dialog__body-nav-item:focus{background-color:rgba(32,122,183,.1)}.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid #207ab7;color:#207ab7}@media (forced-colors:active){.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid highlight;color:highlight}}.tox .tox-dialog__body-content{box-sizing:border-box;display:flex;flex:1;flex-direction:column;max-height:min(650px,calc(100vh - 110px));overflow:auto;-webkit-overflow-scrolling:touch;padding:16px 16px}.tox .tox-dialog__body-content>*{margin-bottom:0;margin-top:16px}.tox .tox-dialog__body-content>:first-child{margin-top:0}.tox .tox-dialog__body-content>:last-child{margin-bottom:0}.tox .tox-dialog__body-content>:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content a{color:#207ab7;cursor:pointer;text-decoration:underline}.tox .tox-dialog__body-content a:focus,.tox .tox-dialog__body-content a:hover{color:#114060;text-decoration:underline}.tox .tox-dialog__body-content a:focus-visible{border-radius:1px;outline:2px solid #207ab7;outline-offset:2px}.tox .tox-dialog__body-content a:active{color:#092335;text-decoration:underline}.tox .tox-dialog__body-content svg{fill:#222f3e}.tox .tox-dialog__body-content strong{font-weight:700}.tox .tox-dialog__body-content ul{list-style-type:disc}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{padding-inline-start:2.5rem}.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{margin-bottom:16px}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content dt,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{display:block;margin-inline-end:0;margin-inline-start:0}.tox .tox-dialog__body-content .tox-form__group h1{color:#222f3e;font-size:20px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group h2{color:#222f3e;font-size:16px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group p{margin-bottom:16px}.tox .tox-dialog__body-content .tox-form__group h1:first-child,.tox .tox-dialog__body-content .tox-form__group h2:first-child,.tox .tox-dialog__body-content .tox-form__group p:first-child{margin-top:0}.tox .tox-dialog__body-content .tox-form__group h1:last-child,.tox .tox-dialog__body-content .tox-form__group h2:last-child,.tox .tox-dialog__body-content .tox-form__group p:last-child{margin-bottom:0}.tox .tox-dialog__body-content .tox-form__group h1:only-child,.tox .tox-dialog__body-content .tox-form__group h2:only-child,.tox .tox-dialog__body-content .tox-form__group p:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center{text-align:center}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end{text-align:end}.tox .tox-dialog--width-lg{height:650px;max-width:1200px}.tox .tox-dialog--fullscreen{height:100%;max-width:100%}.tox .tox-dialog--fullscreen .tox-dialog__body-content{max-height:100%}.tox .tox-dialog--width-md{max-width:800px}.tox .tox-dialog--width-md .tox-dialog__body-content{overflow:auto}.tox .tox-dialog__body-content--centered{text-align:center}.tox .tox-dialog__footer{align-items:center;background-color:#fff;border-top:1px solid #ccc;display:flex;justify-content:space-between;padding:8px 16px}.tox .tox-dialog__footer-end,.tox .tox-dialog__footer-start{display:flex}.tox .tox-dialog__busy-spinner{align-items:center;background-color:rgba(255,255,255,.75);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:3}.tox .tox-dialog__table{border-collapse:collapse;width:100%}.tox .tox-dialog__table thead th{font-weight:700;padding-bottom:8px}.tox .tox-dialog__table thead th:first-child{padding-right:8px}.tox .tox-dialog__table tbody tr{border-bottom:1px solid #404040}.tox .tox-dialog__table tbody tr:last-child{border-bottom:none}.tox .tox-dialog__table td{padding-bottom:8px;padding-top:8px}.tox .tox-dialog__table td:first-child{padding-right:8px}.tox .tox-dialog__iframe{min-height:200px}.tox .tox-dialog__iframe.tox-dialog__iframe--opaque{background:#fff}.tox .tox-navobj-bordered{position:relative}.tox .tox-navobj-bordered::before{border:1px solid #ccc;border-radius:3px;content:'';inset:0;opacity:1;pointer-events:none;position:absolute;z-index:1}.tox .tox-navobj-bordered iframe{border-radius:3px}.tox .tox-navobj-bordered-focus.tox-navobj-bordered::before{border-color:#207ab7;box-shadow:none;outline:2px solid rgba(32,122,183,.25)}.tox .tox-dialog__popups{position:absolute;width:100%;z-index:1100}.tox .tox-dialog__body-iframe{display:flex;flex:1;flex-direction:column}.tox .tox-dialog__body-iframe .tox-navobj{display:flex;flex:1}.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex:1;height:100%}.tox .tox-dialog-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-dialog-dock-fadein{opacity:1;visibility:visible}.tox .tox-dialog-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein{transition-delay:0s}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child){margin-left:8px}}.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left:8px}.tox[dir=rtl] .tox-dialog__body{text-align:right}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav{margin-left:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child){margin-right:8px}}.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right:8px}body.tox-dialog__disable-scroll{overflow:hidden}.tox .tox-dropzone-container{display:flex;flex:1}.tox .tox-dropzone{align-items:center;background:#fff;border:2px dashed #ccc;box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;min-height:100px;padding:10px}.tox .tox-dropzone p{color:rgba(34,47,62,.7);margin:0 0 16px 0}.tox .tox-edit-area{display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-edit-area::before{border:0 solid transparent;border-radius:4px;content:'';inset:0;opacity:0;pointer-events:none;position:absolute;transition:opacity .15s;z-index:1}@media (forced-colors:active){.tox .tox-edit-area::before{border:0 solid highlight}}.tox .tox-edit-area__iframe{background-color:#fff;border:0;box-sizing:border-box;flex:1;height:100%;position:absolute;width:100%}.tox.tox-edit-focus .tox-edit-area::before{opacity:1}.tox.tox-inline-edit-area{border:1px dotted #ccc}.tox .tox-editor-container{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-editor-header{display:grid;grid-template-columns:1fr min-content;z-index:2}.tox:not(.tox-tinymce-inline) .tox-editor-header{background-color:#fff;border-bottom:none;box-shadow:none;padding:4px 0}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition){transition:box-shadow .5s}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header{border-top:1px solid #ccc;box-shadow:none}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:#fff;box-shadow:0 4px 4px -3px rgba(0,0,0,.25);padding:4px 0}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header{box-shadow:0 4px 4px -3px rgba(0,0,0,.25)}.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty{background:0 0;border:none;box-shadow:none;padding:0}.tox-editor-dock-fadeout{opacity:0;visibility:hidden}.tox-editor-dock-fadein{opacity:1;visibility:visible}.tox-editor-dock-transition{transition:visibility 0s linear .25s,opacity .25s ease}.tox-editor-dock-transition.tox-editor-dock-fadein{transition-delay:0s}.tox .tox-control-wrap{flex:1;position:relative}.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display:none}.tox .tox-control-wrap svg{display:block}.tox .tox-control-wrap__status-icon-wrap{position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-control-wrap__status-icon-invalid svg{fill:#c00}.tox .tox-control-wrap__status-icon-unknown svg{fill:orange}.tox .tox-control-wrap__status-icon-valid svg{fill:green}.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right:32px}.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right:4px}.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left:32px}.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left:4px}.tox .tox-custom-preview{border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;flex:1;padding:8px}.tox .tox-autocompleter{max-width:25em}.tox .tox-autocompleter .tox-menu{box-sizing:border-box;max-width:25em}.tox .tox-autocompleter .tox-autocompleter-highlight{font-weight:700}.tox .tox-color-input{display:flex;position:relative;z-index:1}.tox .tox-color-input .tox-textfield{z-index:-1}.tox .tox-color-input span{border-color:rgba(34,47,62,.2);border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;height:24px;position:absolute;top:6px;width:24px}@media (forced-colors:active){.tox .tox-color-input span{border-color:currentColor;border-width:2px!important;forced-color-adjust:none}}.tox .tox-color-input span:focus:not([aria-disabled=true]),.tox .tox-color-input span:hover:not([aria-disabled=true]){border-color:#207ab7;cursor:pointer}.tox .tox-color-input span::before{background-image:linear-gradient(45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(-45deg,rgba(0,0,0,.25) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(0,0,0,.25) 75%),linear-gradient(-45deg,transparent 75%,rgba(0,0,0,.25) 75%);background-position:0 0,0 6px,6px -6px,-6px 0;background-size:12px 12px;border:1px solid #fff;border-radius:3px;box-sizing:border-box;content:'';height:24px;left:-1px;position:absolute;top:-1px;width:24px;z-index:-1}@media (forced-colors:active){.tox .tox-color-input span::before{border:none}}.tox .tox-color-input span[aria-disabled=true]{cursor:not-allowed}.tox:not([dir=rtl]) .tox-color-input .tox-textfield{padding-left:36px}.tox:not([dir=rtl]) .tox-color-input span{left:6px}.tox[dir=rtl] .tox-color-input .tox-textfield{padding-right:36px}.tox[dir=rtl] .tox-color-input span{right:6px}.tox .tox-label,.tox .tox-toolbar-label{color:rgba(34,47,62,.7);display:block;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;padding:0 8px 0 0;text-transform:none;white-space:nowrap}.tox .tox-toolbar-label{padding:0 8px}.tox[dir=rtl] .tox-label{padding:0 0 0 8px}.tox .tox-form{display:flex;flex:1;flex-direction:column}.tox .tox-form__group{box-sizing:border-box;margin-bottom:4px}.tox .tox-form-group--maximize{flex:1}.tox .tox-form__group--error{color:#c00}.tox .tox-form__group--collection{display:flex}.tox .tox-form__grid{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.tox .tox-form__grid--2col>.tox-form__group{width:calc(50% - (8px / 2))}.tox .tox-form__grid--3col>.tox-form__group{width:calc(100% / 3 - (8px / 2))}.tox .tox-form__grid--4col>.tox-form__group{width:calc(25% - (8px / 2))}.tox .tox-form__controls-h-stack{align-items:center;display:flex}.tox .tox-form__group--inline{align-items:center;display:flex}.tox .tox-form__group--stretched{display:flex;flex:1;flex-direction:column}.tox .tox-form__group--stretched .tox-textarea{flex:1}.tox .tox-form__group--stretched .tox-navobj{display:flex;flex:1}.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex:1;height:100%}.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right:4px}.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display:none}.tox .tox-listboxfield .tox-listbox--select,.tox .tox-textarea,.tox .tox-textarea-wrap .tox-textarea:focus,.tox .tox-textfield,.tox .tox-toolbar-textfield{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#222f3e;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-textarea[disabled],.tox .tox-textfield[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-custom-editor:focus-within,.tox .tox-listboxfield .tox-listbox--select:focus,.tox .tox-textarea-wrap:focus-within,.tox .tox-textarea:focus,.tox .tox-textfield:focus{background-color:#fff;border-color:#207ab7;box-shadow:none;outline:2px solid rgba(32,122,183,.25)}.tox .tox-toolbar-textfield{border-width:0;margin-bottom:3px;margin-top:2px;max-width:250px}.tox .tox-naked-btn{background-color:transparent;border:0;border-color:transparent;box-shadow:unset;color:#207ab7;cursor:pointer;display:block;margin:0;padding:0}.tox .tox-naked-btn svg{display:block;fill:#222f3e}.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left:4px}.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right:4px}.tox .tox-listboxfield{cursor:pointer;position:relative}.tox .tox-listboxfield .tox-listbox--select[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-listbox__select-label{cursor:default;flex:1;margin:0 4px}.tox .tox-listbox__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-listbox__select-chevron svg{fill:#222f3e}@media (forced-colors:active){.tox .tox-listbox__select-chevron svg{fill:currentColor!important}}.tox .tox-listboxfield .tox-listbox--select{align-items:center;display:flex}.tox:not([dir=rtl]) .tox-listboxfield svg{right:8px}.tox[dir=rtl] .tox-listboxfield svg{left:8px}.tox .tox-selectfield{cursor:pointer;position:relative}.tox .tox-selectfield select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#222f3e;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-selectfield select[disabled]{background-color:#f2f2f2;color:rgba(34,47,62,.85);cursor:not-allowed}.tox .tox-selectfield select::-ms-expand{display:none}.tox .tox-selectfield select:focus{background-color:#fff;border-color:#207ab7;box-shadow:none;outline:2px solid rgba(32,122,183,.25)}.tox .tox-selectfield svg{pointer-events:none;position:absolute;top:50%;transform:translateY(-50%)}.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"]{padding-right:24px}.tox:not([dir=rtl]) .tox-selectfield svg{right:8px}.tox[dir=rtl] .tox-selectfield select[size=\"0\"],.tox[dir=rtl] .tox-selectfield select[size=\"1\"]{padding-left:24px}.tox[dir=rtl] .tox-selectfield svg{left:8px}.tox .tox-textarea-wrap{border-color:#ccc;border-radius:3px;border-style:solid;border-width:1px;display:flex;flex:1;overflow:hidden}.tox .tox-textarea{-webkit-appearance:textarea;-moz-appearance:textarea;appearance:textarea;white-space:pre-wrap}.tox .tox-textarea-wrap .tox-textarea{border:none}.tox .tox-textarea-wrap .tox-textarea:focus{border:none}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}.tox .tox-help__more-link{list-style:none;margin-top:1em}.tox .tox-imagepreview{background-color:#666;height:380px;overflow:hidden;position:relative;width:100%}.tox .tox-imagepreview.tox-imagepreview__loaded{overflow:auto}.tox .tox-imagepreview__container{display:flex;left:100vw;position:absolute;top:100vw}.tox .tox-imagepreview__image{background:url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==)}.tox .tox-image-tools .tox-spacer{flex:1}.tox .tox-image-tools .tox-bar{align-items:center;display:flex;height:60px;justify-content:center}.tox .tox-image-tools .tox-imagepreview,.tox .tox-image-tools .tox-imagepreview+.tox-bar{margin-top:8px}.tox .tox-image-tools .tox-croprect-block{background:#000;opacity:.5;position:absolute;zoom:1}.tox .tox-image-tools .tox-croprect-handle{border:2px solid #fff;height:20px;left:0;position:absolute;top:0;width:20px}.tox .tox-image-tools .tox-croprect-handle-move{border:0;cursor:move;position:absolute}.tox .tox-image-tools .tox-croprect-handle-nw{border-width:2px 0 0 2px;cursor:nw-resize;left:100px;margin:-2px 0 0 -2px;top:100px}.tox .tox-image-tools .tox-croprect-handle-ne{border-width:2px 2px 0 0;cursor:ne-resize;left:200px;margin:-2px 0 0 -20px;top:100px}.tox .tox-image-tools .tox-croprect-handle-sw{border-width:0 0 2px 2px;cursor:sw-resize;left:100px;margin:-20px 2px 0 -2px;top:200px}.tox .tox-image-tools .tox-croprect-handle-se{border-width:0 2px 2px 0;cursor:se-resize;left:200px;margin:-20px 0 0 -20px;top:200px}.tox .tox-insert-table-picker{background-color:#fff;display:flex;flex-wrap:wrap;width:170px}.tox .tox-insert-table-picker>div{border-color:#ccc;border-style:solid;border-width:0 1px 1px 0;box-sizing:border-box;height:17px;width:17px}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:0 -4px}.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color:rgba(32,122,183,.5);border-color:rgba(32,122,183,.5)}@media (forced-colors:active){.tox .tox-insert-table-picker .tox-insert-table-picker__selected{border-color:Highlight;filter:contrast(50%)}}.tox .tox-insert-table-picker__label{color:rgba(34,47,62,.7);display:block;font-size:14px;padding:4px;text-align:center;width:100%}.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right:0}.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right:0}.tox .tox-menu{background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 4px 8px 0 rgba(34,47,62,.1);display:inline-block;overflow:hidden;vertical-align:top;z-index:1150}.tox .tox-menu.tox-collection.tox-collection--list{padding:0 0}.tox .tox-menu.tox-collection.tox-collection--toolbar{padding:4px}.tox .tox-menu.tox-collection.tox-collection--grid{padding:4px}@media only screen and (min-width:768px){.tox .tox-menu .tox-collection__item-label{overflow-wrap:break-word;word-break:normal}.tox .tox-dialog__popups .tox-menu .tox-collection__item-label{word-break:break-all}}.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin:0}.tox .tox-menubar{background:url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E\") left 0 top 0 #fff;background-color:#fff;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;grid-column:1/-1;grid-row:1;padding:0 4px 0 4px}.tox .tox-promotion+.tox-menubar{grid-column:1}.tox .tox-promotion{background:url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E\") left 0 top 0 #fff;background-color:#fff;grid-column:2;grid-row:1;padding-inline-end:8px;padding-inline-start:4px;padding-top:5px}.tox .tox-promotion-link{align-items:unsafe center;background-color:#e8f1f8;border-radius:5px;color:#086be6;cursor:pointer;display:flex;font-size:14px;height:26.6px;padding:4px 8px;white-space:nowrap}.tox .tox-promotion-link:hover{background-color:#b4d7ff}.tox .tox-promotion-link:focus{background-color:#d9edf7}.tox .tox-mbtn{align-items:center;background:#fff;border:0;border-radius:3px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;padding:0 4px;text-transform:none;width:auto}.tox .tox-mbtn[disabled]{background-color:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-mbtn:focus:not(:disabled){background:#dee0e2;border:0;box-shadow:none;color:#222f3e;position:relative;z-index:1}.tox .tox-mbtn:focus:not(:disabled)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-mbtn:focus:not(:disabled)::after{border:2px solid highlight}}.tox .tox-mbtn--active,.tox .tox-mbtn:not(:disabled).tox-mbtn--active:focus{background:#c8cbcf;border:0;box-shadow:none;color:#222f3e}.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active){background:#dee0e2;border:0;box-shadow:none;color:#222f3e}.tox .tox-mbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor:not-allowed}.tox .tox-mbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px;display:none}.tox .tox-notification{border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;display:grid;font-size:14px;font-weight:400;grid-template-columns:minmax(40px,1fr) auto minmax(40px,1fr);margin-left:auto;margin-right:auto;margin-top:4px;opacity:0;padding:4px;transition:transform .1s ease-in,opacity 150ms ease-in;width:-moz-max-content;width:max-content}.tox .tox-notification a{cursor:pointer;text-decoration:underline}.tox .tox-notification p{font-size:14px;font-weight:400}.tox .tox-notification:focus{border-color:#207ab7;box-shadow:none}.tox .tox-notification--in{opacity:1}.tox .tox-notification--success{background-color:#e4eeda;border-color:#d7e6c8;color:#222f3e}.tox .tox-notification--success p{color:#222f3e}.tox .tox-notification--success a{color:#517342}.tox .tox-notification--success a:focus,.tox .tox-notification--success a:hover{color:#24321d;text-decoration:underline}.tox .tox-notification--success a:focus-visible{border-radius:1px;outline:2px solid #517342;outline-offset:2px}.tox .tox-notification--success a:active{color:#0d120a;text-decoration:underline}.tox .tox-notification--success svg{fill:#222f3e}.tox .tox-notification--error{background-color:#f5cccc;border-color:#f0b3b3;color:#222f3e}.tox .tox-notification--error p{color:#222f3e}.tox .tox-notification--error a{color:#77181f}.tox .tox-notification--error a:focus,.tox .tox-notification--error a:hover{color:#220709;text-decoration:underline}.tox .tox-notification--error a:focus-visible{border-radius:1px;outline:2px solid #77181f;outline-offset:2px}.tox .tox-notification--error a:active{color:#000;text-decoration:underline}.tox .tox-notification--error svg{fill:#222f3e}.tox .tox-notification--warn,.tox .tox-notification--warning{background-color:#fff5cc;border-color:#fff0b3;color:#222f3e}.tox .tox-notification--warn p,.tox .tox-notification--warning p{color:#222f3e}.tox .tox-notification--warn a,.tox .tox-notification--warning a{color:#7a6e25}.tox .tox-notification--warn a:focus,.tox .tox-notification--warn a:hover,.tox .tox-notification--warning a:focus,.tox .tox-notification--warning a:hover{color:#2c280d;text-decoration:underline}.tox .tox-notification--warn a:focus-visible,.tox .tox-notification--warning a:focus-visible{border-radius:1px;outline:2px solid #7a6e25;outline-offset:2px}.tox .tox-notification--warn a:active,.tox .tox-notification--warning a:active{color:#050502;text-decoration:underline}.tox .tox-notification--warn svg,.tox .tox-notification--warning svg{fill:#222f3e}.tox .tox-notification--info{background-color:#d6e7fb;border-color:#c1dbf9;color:#222f3e}.tox .tox-notification--info p{color:#222f3e}.tox .tox-notification--info a{color:#2a64a6}.tox .tox-notification--info a:focus,.tox .tox-notification--info a:hover{color:#163355;text-decoration:underline}.tox .tox-notification--info a:focus-visible{border-radius:1px;outline:2px solid #2a64a6;outline-offset:2px}.tox .tox-notification--info a:active{color:#0b1a2c;text-decoration:underline}.tox .tox-notification--info svg{fill:#222f3e}.tox .tox-notification__body{align-self:center;color:#222f3e;font-size:14px;grid-column-end:3;grid-column-start:2;grid-row-end:2;grid-row-start:1;text-align:center;white-space:normal;word-break:break-all;word-break:break-word}.tox .tox-notification__body>*{margin:0}.tox .tox-notification__body>*+*{margin-top:1rem}.tox .tox-notification__icon{align-self:center;grid-column-end:2;grid-column-start:1;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification__icon svg{display:block}.tox .tox-notification__dismiss{align-self:start;grid-column-end:4;grid-column-start:3;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification .tox-progress-bar{grid-column-end:4;grid-column-start:1;grid-row-end:3;grid-row-start:2;justify-self:center}.tox .tox-notification-container-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-notification-container-dock-fadein{opacity:1;visibility:visible}.tox .tox-notification-container-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-notification-container-dock-transition.tox-notification-container-dock-fadein{transition-delay:0s}.tox .tox-pop{display:inline-block;position:relative}.tox .tox-pop--resizing{transition:width .1s ease}.tox .tox-pop--resizing .tox-toolbar,.tox .tox-pop--resizing .tox-toolbar__group{flex-wrap:nowrap}.tox .tox-pop--transition{transition:.15s ease;transition-property:left,right,top,bottom}.tox .tox-pop--transition::after,.tox .tox-pop--transition::before{transition:all .15s,visibility 0s,opacity 75ms ease 75ms}.tox .tox-pop__dialog{background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);min-width:0;overflow:hidden}.tox .tox-pop__dialog>:not(.tox-toolbar){margin:4px 4px 4px 8px}.tox .tox-pop__dialog .tox-toolbar{background-color:transparent;margin-bottom:-1px}.tox .tox-pop::after,.tox .tox-pop::before{border-style:solid;content:'';display:block;height:0;opacity:1;position:absolute;width:0}@media (forced-colors:active){.tox .tox-pop::after,.tox .tox-pop::before{content:none}}.tox .tox-pop.tox-pop--inset::after,.tox .tox-pop.tox-pop--inset::before{opacity:0;transition:all 0s .15s,visibility 0s,opacity 75ms ease}.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{left:50%;top:100%}.tox .tox-pop.tox-pop--bottom::after{border-color:#fff transparent transparent transparent;border-width:8px;margin-left:-8px;margin-top:-1px}.tox .tox-pop.tox-pop--bottom::before{border-color:#ccc transparent transparent transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{left:50%;top:0;transform:translateY(-100%)}.tox .tox-pop.tox-pop--top::after{border-color:transparent transparent #fff transparent;border-width:8px;margin-left:-8px;margin-top:1px}.tox .tox-pop.tox-pop--top::before{border-color:transparent transparent #ccc transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{left:0;top:calc(50% - 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--left::after{border-color:transparent #fff transparent transparent;border-width:8px;margin-left:-15px}.tox .tox-pop.tox-pop--left::before{border-color:transparent #ccc transparent transparent;border-width:10px;margin-left:-19px}.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{left:100%;top:calc(50% + 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--right::after{border-color:transparent transparent transparent #fff;border-width:8px;margin-left:-1px}.tox .tox-pop.tox-pop--right::before{border-color:transparent transparent transparent #ccc;border-width:10px;margin-left:-1px}.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left:20px}.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left:calc(100% - 20px)}.tox .tox-sidebar-wrap{display:flex;flex-direction:row;flex-grow:1;min-height:0}.tox .tox-sidebar{background-color:#fff;display:flex;flex-direction:row;justify-content:flex-end}.tox .tox-sidebar__slider{display:flex;overflow:hidden}.tox .tox-sidebar__pane-container{display:flex}.tox .tox-sidebar__pane{display:flex}.tox .tox-sidebar--sliding-closed{opacity:0}.tox .tox-sidebar--sliding-open{opacity:1}.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition:width .5s ease,opacity .5s ease}.tox .tox-selector{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;display:inline-block;height:10px;position:absolute;width:10px}.tox.tox-platform-touch .tox-selector{height:12px;width:12px}.tox .tox-slider{align-items:center;display:flex;flex:1;height:24px;justify-content:center;position:relative}.tox .tox-slider__rail{background-color:transparent;border:1px solid #ccc;border-radius:3px;height:10px;min-width:120px;width:100%}.tox .tox-slider__handle{background-color:#207ab7;border:2px solid #185d8c;border-radius:3px;box-shadow:none;height:24px;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:14px}.tox .tox-form__controls-h-stack>.tox-slider:not(:first-of-type){margin-inline-start:8px}.tox .tox-form__controls-h-stack>.tox-form__group+.tox-slider{margin-inline-start:32px}.tox .tox-form__controls-h-stack>.tox-slider+.tox-form__group{margin-inline-start:32px}.tox .tox-source-code{overflow:auto}.tox .tox-spinner{display:flex}.tox .tox-spinner>div{animation:tam-bouncing-dots 1.5s ease-in-out 0s infinite both;background-color:rgba(34,47,62,.7);border-radius:100%;height:8px;width:8px}.tox .tox-spinner>div:nth-child(1){animation-delay:-.32s}.tox .tox-spinner>div:nth-child(2){animation-delay:-.16s}@keyframes tam-bouncing-dots{0%,100%,80%{transform:scale(0)}40%{transform:scale(1)}}.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right:4px}.tox .tox-statusbar{align-items:center;background-color:#fff;border-top:1px solid #ccc;color:rgba(34,47,62,.7);display:flex;flex:0 0 auto;font-size:12px;font-weight:400;height:18px;overflow:hidden;padding:0 8px;position:relative;text-transform:uppercase}.tox .tox-statusbar__path{display:flex;flex:1 1 auto;text-overflow:ellipsis;white-space:nowrap}.tox .tox-statusbar__right-container{display:flex;justify-content:flex-end;white-space:nowrap}.tox .tox-statusbar__help-text{text-align:center}.tox .tox-statusbar__text-container{align-items:flex-start;display:flex;flex:1 1 auto;height:16px;justify-content:space-between;overflow:hidden}@media only screen and (min-width:768px){.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__help-text,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__path,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__right-container{flex:0 0 calc(100% / 3)}}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-end{justify-content:flex-end}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-start{justify-content:flex-start}.tox .tox-statusbar__text-container.tox-statusbar__text-container--space-around{justify-content:space-around}.tox .tox-statusbar__path>*{display:inline;white-space:nowrap}.tox .tox-statusbar__wordcount{flex:0 0 auto;margin-left:1ch}@media only screen and (max-width:767px){.tox .tox-statusbar__text-container .tox-statusbar__help-text{display:none}.tox .tox-statusbar__text-container .tox-statusbar__help-text:only-child{display:block}}.tox .tox-statusbar a,.tox .tox-statusbar__path-item,.tox .tox-statusbar__wordcount{color:rgba(34,47,62,.7);position:relative;text-decoration:none}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){color:#222f3e;cursor:pointer}.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border:2px solid highlight}}.tox .tox-statusbar__branding svg{fill:rgba(34,47,62,.8);height:1em;margin-left:.3em;width:auto}@media (forced-colors:active){.tox .tox-statusbar__branding svg{fill:currentColor}}.tox .tox-statusbar__branding a{align-items:center;display:inline-flex}.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg,.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg{fill:#222f3e}.tox .tox-statusbar__resize-handle{align-items:flex-end;align-self:stretch;cursor:nwse-resize;display:flex;flex:0 0 auto;justify-content:flex-end;margin-bottom:3px;margin-left:4px;margin-right:calc(3px - 8px);margin-top:3px;padding-bottom:0;padding-left:0;padding-right:0;position:relative}.tox .tox-statusbar__resize-handle svg{display:block;fill:rgba(34,47,62,.5)}.tox .tox-statusbar__resize-handle:focus svg,.tox .tox-statusbar__resize-handle:hover svg{fill:#222f3e}.tox .tox-statusbar__resize-handle:focus-visible{background-color:transparent;border-radius:1px 1px -4px 1px;box-shadow:0 0 0 2px transparent}.tox .tox-statusbar__resize-handle:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar__resize-handle:focus-visible::after{border:2px solid highlight}}.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right:4px}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:2ch}.tox[dir=rtl] .tox-statusbar{flex-direction:row-reverse}.tox[dir=rtl] .tox-statusbar__path>*{margin-left:4px}.tox[dir=rtl] .tox-statusbar__branding svg{margin-left:0;margin-right:.3em}.tox .tox-throbber{z-index:1299}.tox .tox-throbber__busy-spinner{align-items:center;background-color:rgba(255,255,255,.6);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.tox .tox-tbtn{align-items:center;background:#fff;border:0;border-radius:3px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:3px 0 2px 0;outline:0;padding:0;text-transform:none;width:34px}@media (forced-colors:active){.tox .tox-tbtn.tox-tbtn:hover,.tox .tox-tbtn:hover{outline:1px dashed currentColor}.tox .tox-tbtn.tox-tbtn--active,.tox .tox-tbtn.tox-tbtn--enabled,.tox .tox-tbtn.tox-tbtn--enabled:focus,.tox .tox-tbtn.tox-tbtn--enabled:hover,.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){outline:1px solid currentColor;position:relative}}.tox .tox-tbtn svg{display:block;fill:#222f3e}@media (forced-colors:active){.tox .tox-tbtn svg{fill:currentColor!important}.tox .tox-tbtn svg.tox-tbtn--enabled,.tox .tox-tbtn svg:focus:not(.tox-tbtn--disabled){fill:currentColor!important}.tox .tox-tbtn svg .tox-tbtn:disabled,.tox .tox-tbtn svg .tox-tbtn:disabled:hover,.tox .tox-tbtn svg.tox-tbtn--disabled,.tox .tox-tbtn svg.tox-tbtn--disabled:hover{filter:contrast(0)}}.tox .tox-tbtn.tox-tbtn-more{padding-left:5px;padding-right:5px;width:inherit}.tox .tox-tbtn:focus{background:#dee0e2;border:0;box-shadow:none;position:relative;z-index:1}.tox .tox-tbtn:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn:focus::after{border:2px solid highlight}}.tox .tox-tbtn:hover{background:#dee0e2;border:0;box-shadow:none;color:#222f3e}.tox .tox-tbtn:hover svg{fill:#222f3e}.tox .tox-tbtn:active{background:#c8cbcf;border:0;box-shadow:none;color:#222f3e}.tox .tox-tbtn:active svg{fill:#222f3e}.tox .tox-tbtn--disabled .tox-tbtn--enabled svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{background:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--active,.tox .tox-tbtn--enabled,.tox .tox-tbtn--enabled:focus,.tox .tox-tbtn--enabled:hover{background:#c8cbcf;border:0;box-shadow:none;color:#222f3e;position:relative}.tox .tox-tbtn--active>*,.tox .tox-tbtn--enabled:focus>*,.tox .tox-tbtn--enabled:hover>*,.tox .tox-tbtn--enabled>*{transform:none}.tox .tox-tbtn--active svg,.tox .tox-tbtn--enabled svg,.tox .tox-tbtn--enabled:focus svg,.tox .tox-tbtn--enabled:hover svg{fill:#222f3e}.tox .tox-tbtn--active.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:focus.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg{fill:rgba(34,47,62,.5)}.tox .tox-tbtn--enabled:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn--enabled:focus::after{border:2px solid highlight}}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){color:#222f3e}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg{fill:#222f3e}.tox .tox-tbtn:active>*{transform:none}.tox .tox-tbtn--md{height:51px;width:51px}.tox .tox-tbtn--lg{flex-direction:column;height:68px;width:68px}.tox .tox-tbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tbtn--labeled{padding:0 4px;width:unset}.tox .tox-tbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-number-input{background:0 0;border-radius:3px;display:flex;margin:3px 0 2px 0;position:relative;width:auto}.tox .tox-number-input:focus{background:#dee0e2}.tox .tox-number-input:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper{display:flex;pointer-events:none;position:relative;text-align:center}.tox .tox-number-input .tox-input-wrapper:focus{background-color:#dee0e2;z-index:1}.tox .tox-number-input .tox-input-wrapper:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border:2px solid highlight}}.tox .tox-number-input input{border-radius:3px;color:#222f3e;font-size:14px;margin:2px 0;pointer-events:all;position:relative;width:60px}.tox .tox-number-input input:hover{background:#dee0e2;color:#222f3e}.tox .tox-number-input input:focus{background-color:#dee0e2}.tox .tox-number-input input:disabled{background:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-number-input button{color:#222f3e;height:34px;position:relative;text-align:center;width:24px}@media (forced-colors:active){.tox .tox-number-input button:active,.tox .tox-number-input button:focus,.tox .tox-number-input button:hover{outline:1px solid currentColor!important}}.tox .tox-number-input button svg{display:block;fill:#222f3e;margin:0 auto;transform:scale(.67)}@media (forced-colors:active){.tox .tox-number-input button svg,.tox .tox-number-input button svg:active,.tox .tox-number-input button svg:hover{fill:currentColor!important}.tox .tox-number-input button svg:disabled{filter:contrast(0)}}.tox .tox-number-input button:focus{background:#dee0e2;z-index:1}.tox .tox-number-input button:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input button:focus::after{border:2px solid highlight}}.tox .tox-number-input button:hover{background:#dee0e2;border:0;box-shadow:none;color:#222f3e}.tox .tox-number-input button:hover svg{fill:#222f3e}.tox .tox-number-input button:active{background:#c8cbcf;border:0;box-shadow:none;color:#222f3e}.tox .tox-number-input button:active svg{fill:#222f3e}.tox .tox-number-input button:disabled{background:#fff;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-number-input button:disabled svg{fill:rgba(34,47,62,.5)}.tox .tox-number-input button.minus{border-radius:3px 0 0 3px}.tox .tox-number-input button.plus{border-radius:0 3px 3px 0}.tox .tox-number-input:focus:not(:active)>.tox-input-wrapper,.tox .tox-number-input:focus:not(:active)>button{background:#dee0e2}.tox .tox-tbtn--select{margin:3px 0 2px 0;padding:0 4px;width:auto}.tox .tox-tbtn__select-label{cursor:default;font-weight:400;height:initial;margin:0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-tbtn__select-chevron svg{fill:rgba(34,47,62,.5)}@media (forced-colors:active){.tox .tox-tbtn__select-chevron svg{fill:currentColor}}.tox .tox-tbtn--bespoke{background:0 0}.tox .tox-tbtn--bespoke:focus{background:#dee0e2}.tox .tox-tbtn--bespoke+.tox-tbtn--bespoke{margin-inline-start:0}.tox .tox-tbtn--bespoke .tox-tbtn__select-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:7em}.tox .tox-tbtn--disabled .tox-tbtn__select-label,.tox .tox-tbtn--select:disabled .tox-tbtn__select-label{cursor:not-allowed}.tox .tox-split-button{border:0;border-radius:3px;box-sizing:border-box;display:flex;margin:3px 0 2px 0}.tox .tox-split-button:hover{box-shadow:0 0 0 1px #dee0e2 inset}.tox .tox-split-button:focus{background:#dee0e2;box-shadow:none;color:#222f3e;position:relative;z-index:1}.tox .tox-split-button:focus::after{pointer-events:none;border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-split-button:focus::after{border:2px solid highlight}}.tox .tox-split-button>*{border-radius:0}.tox .tox-split-button>:nth-child(1){border-bottom-left-radius:3px;border-top-left-radius:3px}.tox .tox-split-button>:nth-child(2){border-bottom-right-radius:3px;border-top-right-radius:3px}.tox .tox-split-button__chevron{width:16px}.tox .tox-split-button__chevron svg{fill:rgba(34,47,62,.5)}@media (forced-colors:active){.tox .tox-split-button__chevron svg{fill:currentColor}}.tox .tox-split-button .tox-tbtn{margin:0}.tox .tox-split-button:focus .tox-tbtn{background-color:transparent}.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{background:#fff;box-shadow:none;color:rgba(34,47,62,.5)}.tox.tox-platform-touch .tox-split-button .tox-tbtn--select{padding:0 0}.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child{width:30px}.tox.tox-platform-touch .tox-split-button__chevron{width:20px}.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color,.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color{opacity:.6}.tox .tox-toolbar-overlord{background-color:#fff}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background-attachment:local;background-color:#fff;background-image:repeating-linear-gradient(#ccc 0 1px,transparent 1px 39px);background-position:center top 39px;background-repeat:no-repeat;background-size:calc(100% - 4px * 2) calc(100% - 39px);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 0;transform:perspective(1px)}.tox .tox-toolbar-overlord>.tox-toolbar,.tox .tox-toolbar-overlord>.tox-toolbar__overflow,.tox .tox-toolbar-overlord>.tox-toolbar__primary{background-position:center top 0;background-size:calc(100% - 4px * 2) calc(100% - 0px)}.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height:0;opacity:0;padding-bottom:0;padding-top:0;visibility:hidden}.tox .tox-toolbar__overflow--growing{transition:height .3s ease,opacity .2s linear .1s}.tox .tox-toolbar__overflow--shrinking{transition:opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s}.tox .tox-anchorbar,.tox .tox-toolbar-overlord{grid-column:1/-1}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{border-top:1px solid #ccc;margin-top:-1px;padding-bottom:0;padding-top:0}@media (forced-colors:active){.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{outline:1px solid currentColor}}.tox .tox-toolbar--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-pop .tox-toolbar{border-width:0}.tox .tox-toolbar--no-divider{background-image:none}.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,.tox .tox-toolbar-overlord .tox-toolbar__primary{background-position:center top 39px}.tox .tox-editor-header>.tox-toolbar--scrolling,.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child{background-image:none}.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color:#fff;background-position:center top 43px;background-size:calc(100% - 8px * 2) calc(100% - 51px);border:none;border-radius:3px;box-shadow:0 0 2px 0 rgba(34,47,62,.2),0 4px 8px 0 rgba(34,47,62,.15);overscroll-behavior:none;padding:4px 0}@media (forced-colors:active){.tox.tox-tinymce-aux .tox-toolbar__overflow{border:solid}}.tox-pop .tox-pop__dialog .tox-toolbar{background-position:center top 43px;background-size:calc(100% - 4px * 2) calc(100% - 51px);padding:4px 0}.tox .tox-toolbar__group{align-items:center;display:flex;flex-wrap:wrap;margin:0 0;padding:0 4px 0 4px}.tox .tox-toolbar__group--pull-right{margin-left:auto}.tox .tox-toolbar--scrolling .tox-toolbar__group{flex-shrink:0;flex-wrap:nowrap}.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right:1px solid #ccc}.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left:1px solid #ccc}.tox .tox-tooltip{display:inline-block;max-width:15em;padding:8px;pointer-events:none;position:relative;width:-moz-max-content;width:max-content;z-index:1150}.tox .tox-tooltip__body{background-color:#222f3e;border-radius:3px;box-shadow:none;color:#fff;font-size:12px;font-style:normal;font-weight:600;overflow-wrap:break-word;padding:4px 6px;text-transform:none}@media (forced-colors:active){.tox .tox-tooltip__body{outline:outset 1px}}.tox .tox-tooltip__arrow{position:absolute}.tox .tox-tooltip--down .tox-tooltip__arrow{border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #222f3e;bottom:0;left:50%;position:absolute;transform:translateX(-50%)}.tox .tox-tooltip--up .tox-tooltip__arrow{border-bottom:8px solid #222f3e;border-left:8px solid transparent;border-right:8px solid transparent;left:50%;position:absolute;top:0;transform:translateX(-50%)}.tox .tox-tooltip--right .tox-tooltip__arrow{border-bottom:8px solid transparent;border-left:8px solid #222f3e;border-top:8px solid transparent;position:absolute;right:0;top:50%;transform:translateY(-50%)}.tox .tox-tooltip--left .tox-tooltip__arrow{border-bottom:8px solid transparent;border-right:8px solid #222f3e;border-top:8px solid transparent;left:0;position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-tree{display:flex;flex-direction:column}.tox .tox-tree .tox-trbtn{align-items:center;background:0 0;border:0;border-radius:4px;box-shadow:none;color:#222f3e;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;margin-bottom:4px;margin-top:4px;outline:0;overflow:hidden;padding:0;padding-left:8px;text-transform:none}.tox .tox-tree .tox-trbtn .tox-tree__label{cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tree .tox-trbtn svg{display:block;fill:#222f3e}.tox .tox-tree .tox-trbtn:focus{background:#dee0e2;border:0;box-shadow:none}.tox .tox-tree .tox-trbtn:hover{background:#dee0e2;border:0;box-shadow:none;color:#222f3e}.tox .tox-tree .tox-trbtn:hover svg{fill:#222f3e}.tox .tox-tree .tox-trbtn:active{background:#b1d0e6;border:0;box-shadow:none;color:#222f3e}.tox .tox-tree .tox-trbtn:active svg{fill:#222f3e}.tox .tox-tree .tox-trbtn--disabled,.tox .tox-tree .tox-trbtn--disabled:hover,.tox .tox-tree .tox-trbtn:disabled,.tox .tox-tree .tox-trbtn:disabled:hover{background:0 0;border:0;box-shadow:none;color:rgba(34,47,62,.5);cursor:not-allowed}.tox .tox-tree .tox-trbtn--disabled svg,.tox .tox-tree .tox-trbtn--disabled:hover svg,.tox .tox-tree .tox-trbtn:disabled svg,.tox .tox-tree .tox-trbtn:disabled:hover svg{fill:rgba(34,47,62,.5)}.tox .tox-tree .tox-trbtn--enabled,.tox .tox-tree .tox-trbtn--enabled:hover{background:#b1d0e6;border:0;box-shadow:none;color:#222f3e}.tox .tox-tree .tox-trbtn--enabled:hover>*,.tox .tox-tree .tox-trbtn--enabled>*{transform:none}.tox .tox-tree .tox-trbtn--enabled svg,.tox .tox-tree .tox-trbtn--enabled:hover svg{fill:#222f3e}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled){color:#222f3e}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg{fill:#222f3e}.tox .tox-tree .tox-trbtn:active>*{transform:none}.tox .tox-tree .tox-trbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tree .tox-trbtn--labeled{padding:0 4px;width:unset}.tox .tox-tree .tox-trbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-tree .tox-tree--directory{display:flex;flex-direction:column}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label{font-weight:700}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron{margin-right:6px}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--shrinking) .tox-chevron{transition:transform .5s ease-in-out}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--open) .tox-chevron{transform:rotate(90deg)}.tox .tox-tree .tox-tree--leaf__label{font-weight:400}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg{fill:#222f3e}.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg{fill:#222f3e}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#222f3e}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#222f3e}.tox .tox-tree .tox-tree--directory__children{overflow:hidden;padding-left:16px}.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking{transition:height .5s ease-in-out}.tox .tox-tree .tox-trbtn.tox-tree--leaf__label{display:flex;justify-content:space-between}.tox .tox-revisionhistory__pane{padding:0!important}.tox .tox-revisionhistory__container{display:flex;flex-direction:column;height:100%}.tox .tox-revisionhistory{background-color:#fff;border-radius:4px;border-top:1px solid #ccc;display:flex;flex:1;height:100%;margin-top:8px;overflow-x:auto;overflow-y:hidden;position:relative;width:100%}.tox .tox-revisionhistory--align-right{margin-left:auto}.tox .tox-revisionhistory__iframe{flex:1}.tox .tox-revisionhistory__sidebar{border-left:1px solid #ccc;height:100%;max-width:360px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__sidebar-title{border-bottom:1px solid #ccc;color:#222f3e;font-size:20px;font-weight:400;height:60px;min-width:192px;padding:16px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions{flex-direction:column;max-height:calc(100% - 60px);min-width:192px;overflow-y:auto;padding:8px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus{height:100%;position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0;border-radius:3px;bottom:1px;left:1px;right:1px;top:1px}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card{border:1px solid #ccc;border-radius:3px;color:#222f3e;cursor:pointer;font-size:14px;margin-bottom:8px;padding:8px;text-overflow:ellipsis;text-wrap:nowrap;width:100%}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:hover{background-color:#dee0e2;box-shadow:none;color:#222f3e}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus{position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border-radius:3px!important;border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card.tox-revisionhistory__card--selected{background-color:#b1d0e6;box-shadow:none;color:#222f3e}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__norevision{color:rgba(34,47,62,.7);font-size:16px;line-height:24px;padding:5px 5.5px}.tox .tox-view-wrap,.tox .tox-view-wrap__slot-container{background-color:#fff;display:flex;flex:1;flex-direction:column;height:100%}.tox .tox-view{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-view__header{align-items:center;display:flex;font-size:16px;justify-content:space-between;padding:8px 8px 0 8px;position:relative}.tox .tox-view__label{color:#222f3e;font-weight:700;line-height:24px;padding:4px 16px;text-align:center;white-space:nowrap}.tox .tox-view__label--normal{font-size:16px}.tox .tox-view__label--large{font-size:20px}.tox .tox-view--mobile.tox-view__header,.tox .tox-view--mobile.tox-view__toolbar{padding:8px}.tox .tox-view--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-view__toolbar{display:flex;flex-direction:row;gap:8px;justify-content:space-between;overflow-x:auto;padding:8px 8px 0 8px}.tox .tox-view__toolbar__group{display:flex;flex-direction:row;gap:12px}.tox .tox-view__header-end,.tox .tox-view__header-start{display:flex}.tox .tox-view__pane{height:100%;padding:8px;position:relative;width:100%}.tox .tox-view__pane_panel{border:1px solid #ccc;border-radius:3px}.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end>*,.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start>*{margin-left:8px}.tox[dir=rtl] .tox-view__header .tox-view__header-end>*,.tox[dir=rtl] .tox-view__header .tox-view__header-start>*{margin-right:8px}.tox .tox-well{border:1px solid #ccc;border-radius:3px;padding:8px;width:100%}.tox .tox-well>:first-child{margin-top:0}.tox .tox-well>:last-child{margin-bottom:0}.tox .tox-well>:only-child{margin:0}.tox .tox-custom-editor{border:1px solid #ccc;border-radius:3px;display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-dialog-loading::before{background-color:rgba(0,0,0,.5);content:\"\";height:100%;position:absolute;width:100%;z-index:1000}.tox .tox-tab{cursor:pointer}.tox .tox-dialog__content-js{display:flex;flex:1}.tox .tox-dialog__body-content .tox-collection{display:flex;flex:1}.tox:not(.tox-tinymce-inline) .tox-editor-header{background-color:none;padding:0}.tox.tox-tinymce--toolbar-bottom .tox-editor-header,.tox.tox-tinymce-inline .tox-editor-header{margin-bottom:-1px}.tox.tox-tinymce-inline .tox-editor-container{overflow:hidden}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header{border-top:none;box-shadow:none}.tox.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:transparent;box-shadow:0 4px 4px -3px rgba(0,0,0,.25);padding:0}.tox.tox.tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header{box-shadow:0 4px 4px -3px rgba(0,0,0,.25)}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:-4px 0}.tox .tox-menu.tox-collection.tox-collection--list{padding:0}.tox .tox-pop{box-shadow:none}.tox .tox-number-input,.tox .tox-split-button,.tox .tox-tbtn,.tox .tox-tbtn--select{margin:2px 0 3px 0}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background:url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23cccccc'/%3E%3C/svg%3E\") left 0 top 0 #fff!important}.tox .tox-menubar+.tox-toolbar-overlord{border-top:none}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord .tox-toolbar__primary{border-top:1px solid #ccc;margin-top:-1px}.tox.tox-tinymce-aux .tox-toolbar__overflow{border:1px solid #ccc;padding:0}.tox .tox-pop .tox-pop__dialog .tox-toolbar{padding:0}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar{border-top:1px solid #ccc}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child{border-top:1px solid #ccc}.tox .tox-toolbar__group{padding:0 4px 0 4px}.tox .tox-collection__item{border-radius:0;cursor:pointer}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){color:#222f3e}.tox .tox-statusbar__branding svg{fill:rgba(34,47,62,.8);height:1em;margin-left:.3em;width:auto}@media (forced-colors:active){.tox .tox-statusbar__branding svg{fill:currentColor}}.tox .tox-statusbar__branding a{align-items:center;display:inline-flex}.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg,.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg{fill:#222f3e}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:1ch}.tox[dir=rtl] .tox-statusbar__branding svg{margin-left:0;margin-right:.3em}.tox .tox-statusbar__resize-handle{padding-bottom:0;padding-right:0}.tox .tox-button::before{display:none}") //# sourceMappingURL=skin.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5/skin.shadowdom.js ================================================ tinymce.Resource.add('ui/tinymce-5/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}") //# sourceMappingURL=skin.shadowdom.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5-dark/content.inline.js ================================================ tinymce.Resource.add('ui/tinymce-5-dark/content.inline.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%234C4C4C%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.1)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.1);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #b4d7ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #b4d7ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #b4d7ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #b4d7ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#b4d7ff}.mce-content-body .mce-edit-focus{outline:3px solid #b4d7ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid rgba(180,215,255,.7);bottom:-1px;content:'';left:-1px;mix-blend-mode:multiply;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#b4d7ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}") //# sourceMappingURL=content.inline.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5-dark/content.js ================================================ tinymce.Resource.add('ui/tinymce-5-dark/content.css', ".mce-content-body .mce-item-anchor{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'8'%20height%3D'12'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20d%3D'M0%200L8%200%208%2012%204.09117821%209%200%2012z'%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center}.mce-content-body .mce-item-anchor:empty{cursor:default;display:inline-block;height:12px!important;padding:0 2px;-webkit-user-modify:read-only;-moz-user-modify:read-only;-webkit-user-select:all;-moz-user-select:all;user-select:all;width:8px!important}.mce-content-body .mce-item-anchor:not(:empty){background-position-x:2px;display:inline-block;padding-left:12px}.mce-content-body .mce-item-anchor[data-mce-selected]{outline-offset:1px}.tox-comments-visible .tox-comment[contenteditable=false]:not([data-mce-selected]),.tox-comments-visible span.tox-comment img:not([data-mce-selected]),.tox-comments-visible span.tox-comment span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment>video:not([data-mce-selected]){outline:3px solid #ffe89d}.tox-comments-visible .tox-comment[contenteditable=false][data-mce-annotation-active=true]:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] img:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true] span.mce-preview-object:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>audio:not([data-mce-selected]),.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]>video:not([data-mce-selected]){outline:3px solid #fed635}.tox-comments-visible span.tox-comment:not([data-mce-selected]){background-color:#ffe89d;outline:0}.tox-comments-visible span.tox-comment[data-mce-annotation-active=true]:not([data-mce-selected=inline-boundary]){background-color:#fed635}.tox-checklist>li:not(.tox-checklist--hidden){list-style:none;margin:.25em 0}.tox-checklist>li:not(.tox-checklist--hidden)::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-unchecked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2215%22%20height%3D%2215%22%20x%3D%22.5%22%20y%3D%22.5%22%20fill-rule%3D%22nonzero%22%20stroke%3D%22%236d737b%22%20rx%3D%222%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\");cursor:pointer;height:1em;margin-left:-1.5em;margin-top:.125em;position:absolute;width:1em}.tox-checklist li:not(.tox-checklist--hidden).tox-checklist--checked::before{content:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2216%22%20height%3D%2216%22%20viewBox%3D%220%200%2016%2016%22%3E%3Cg%20id%3D%22checklist-checked%22%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Crect%20id%3D%22Rectangle%22%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22%234099FF%22%20fill-rule%3D%22nonzero%22%20rx%3D%222%22%2F%3E%3Cpath%20id%3D%22Path%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%20d%3D%22M11.5703186%2C3.14417309%20C11.8516238%2C2.73724603%2012.4164781%2C2.62829933%2012.83558%2C2.89774797%20C13.260121%2C3.17069355%2013.3759736%2C3.72932262%2013.0909105%2C4.14168582%20L7.7580587%2C11.8560195%20C7.43776896%2C12.3193404%206.76483983%2C12.3852142%206.35607322%2C11.9948725%20L3.02491697%2C8.8138662%20C2.66090143%2C8.46625845%202.65798871%2C7.89594698%203.01850234%2C7.54483354%20C3.373942%2C7.19866177%203.94940006%2C7.19592841%204.30829608%2C7.5386474%20L6.85276923%2C9.9684299%20L11.5703186%2C3.14417309%20Z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E%0A\")}[dir=rtl] .tox-checklist>li:not(.tox-checklist--hidden)::before{margin-left:0;margin-right:-1.5em}code[class*=language-],pre[class*=language-]{color:#f8f8f2;background:0 0;text-shadow:0 1px rgba(0,0,0,.3);font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border-radius:.3em}:not(pre)>code[class*=language-],pre[class*=language-]{background:#282a36}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#6272a4}.token.punctuation{color:#f8f8f2}.namespace{opacity:.7}.token.constant,.token.deleted,.token.property,.token.symbol,.token.tag{color:#ff79c6}.token.boolean,.token.number{color:#bd93f9}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#50fa7b}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f8f8f2}.token.atrule,.token.attr-value,.token.class-name,.token.function{color:#f1fa8c}.token.keyword{color:#8be9fd}.token.important,.token.regex{color:#ffb86c}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.mce-content-body{overflow-wrap:break-word;word-wrap:break-word}.mce-content-body .mce-visual-caret{background-color:#000;background-color:currentColor;position:absolute}.mce-content-body .mce-visual-caret-hidden{display:none}.mce-content-body [data-mce-caret]{left:-1000px;margin:0;padding:0;position:absolute;right:auto;top:0}.mce-content-body .mce-offscreen-selection{left:-2000000px;max-width:1000000px;position:absolute}.mce-content-body [contentEditable=false]{cursor:default}.mce-content-body [contentEditable=true]{cursor:text}.tox-cursor-format-painter{cursor:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M15%2C6%20C15%2C5.45%2014.55%2C5%2014%2C5%20L6%2C5%20C5.45%2C5%205%2C5.45%205%2C6%20L5%2C10%20C5%2C10.55%205.45%2C11%206%2C11%20L14%2C11%20C14.55%2C11%2015%2C10.55%2015%2C10%20L15%2C9%20L16%2C9%20L16%2C12%20L9%2C12%20L9%2C19%20C9%2C19.55%209.45%2C20%2010%2C20%20L11%2C20%20C11.55%2C20%2012%2C19.55%2012%2C19%20L12%2C14%20L18%2C14%20L18%2C7%20L15%2C7%20L15%2C6%20Z%22%2F%3E%0A%20%20%20%20%3Cpath%20fill%3D%22%23000%22%20fill-rule%3D%22nonzero%22%20d%3D%22M1%2C1%20L8.25%2C1%20C8.66421356%2C1%209%2C1.33578644%209%2C1.75%20L9%2C1.75%20C9%2C2.16421356%208.66421356%2C2.5%208.25%2C2.5%20L2.5%2C2.5%20L2.5%2C8.25%20C2.5%2C8.66421356%202.16421356%2C9%201.75%2C9%20L1.75%2C9%20C1.33578644%2C9%201%2C8.66421356%201%2C8.25%20L1%2C1%20Z%22%2F%3E%0A%20%20%3C%2Fg%3E%0A%3C%2Fsvg%3E%0A\"),default}div.mce-footnotes hr{margin-inline-end:auto;margin-inline-start:0;width:25%}div.mce-footnotes li>a.mce-footnotes-backlink{text-decoration:none}@media print{sup.mce-footnote a{color:#000;text-decoration:none}div.mce-footnotes{break-inside:avoid;width:100%}div.mce-footnotes li>a.mce-footnotes-backlink{display:none}}tiny-math-block{display:flex;justify-content:center;margin:16px 0 16px 0}tiny-math-inline{display:inline-block}.mce-content-body figure.align-left{float:left}.mce-content-body figure.align-right{float:right}.mce-content-body figure.image.align-center{display:table;margin-left:auto;margin-right:auto}.mce-preview-object{border:1px solid gray;display:inline-block;line-height:0;margin:0 2px 0 2px;position:relative}.mce-preview-object .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.mce-preview-object[data-mce-selected=\"2\"] .mce-shim{display:none}.mce-content-body .mce-mergetag{cursor:default!important;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body .mce-mergetag:hover{background-color:rgba(0,108,231,.3)}.mce-content-body .mce-mergetag-affix{background-color:rgba(0,108,231,.3);color:#006ce7}.mce-object{background:transparent url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%3E%3Cpath%20d%3D%22M4%203h16a1%201%200%200%201%201%201v16a1%201%200%200%201-1%201H4a1%201%200%200%201-1-1V4a1%201%200%200%201%201-1zm1%202v14h14V5H5zm4.79%202.565l5.64%204.028a.5.5%200%200%201%200%20.814l-5.64%204.028a.5.5%200%200%201-.79-.407V7.972a.5.5%200%200%201%20.79-.407z%22%20fill%3D%22%23cccccc%22%2F%3E%3C%2Fsvg%3E%0A\") no-repeat center;border:1px dashed #aaa}.mce-pagebreak{border:1px dashed #aaa;cursor:default;display:block;height:5px;margin-top:15px;page-break-before:always;width:100%}@media print{.mce-pagebreak{border:0}}.tiny-pageembed .mce-shim{background:url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7);height:100%;left:0;position:absolute;top:0;width:100%}.tiny-pageembed[data-mce-selected=\"2\"] .mce-shim{display:none}.tiny-pageembed{display:inline-block;position:relative}.tiny-pageembed--16by9,.tiny-pageembed--1by1,.tiny-pageembed--21by9,.tiny-pageembed--4by3{display:block;overflow:hidden;padding:0;position:relative;width:100%}.tiny-pageembed--21by9{padding-top:42.857143%}.tiny-pageembed--16by9{padding-top:56.25%}.tiny-pageembed--4by3{padding-top:75%}.tiny-pageembed--1by1{padding-top:100%}.tiny-pageembed--16by9 iframe,.tiny-pageembed--1by1 iframe,.tiny-pageembed--21by9 iframe,.tiny-pageembed--4by3 iframe{border:0;height:100%;left:0;position:absolute;top:0;width:100%}.mce-content-body[data-mce-placeholder]{position:relative}.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:rgba(34,47,62,.7);content:attr(data-mce-placeholder);position:absolute}@media (forced-colors:active){.mce-content-body[data-mce-placeholder]:not(.mce-visualblocks)::before{color:highlight;filter:brightness(30%);z-index:-1}}.mce-content-body:not([dir=rtl])[data-mce-placeholder]:not(.mce-visualblocks)::before{left:1px}.mce-content-body[dir=rtl][data-mce-placeholder]:not(.mce-visualblocks)::before{right:1px}.mce-content-body div.mce-resizehandle{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;height:10px;position:absolute;width:10px;z-index:1298}.mce-content-body div.mce-resizehandle:hover{background-color:#4099ff}.mce-content-body div.mce-resizehandle:nth-of-type(1){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(2){cursor:nesw-resize}.mce-content-body div.mce-resizehandle:nth-of-type(3){cursor:nwse-resize}.mce-content-body div.mce-resizehandle:nth-of-type(4){cursor:nesw-resize}.mce-content-body .mce-resize-backdrop{z-index:10000}.mce-content-body .mce-clonedresizable{cursor:default;opacity:.5;outline:1px dashed #000;position:absolute;z-index:10001}.mce-content-body .mce-clonedresizable.mce-resizetable-columns td,.mce-content-body .mce-clonedresizable.mce-resizetable-columns th{border:0}.mce-content-body .mce-resize-helper{background:#555;background:rgba(0,0,0,.75);border:1px;border-radius:3px;color:#fff;display:none;font-family:sans-serif;font-size:12px;line-height:14px;margin:5px 10px;padding:5px;position:absolute;white-space:nowrap;z-index:10002}.tox-rtc-user-selection{position:relative}.tox-rtc-user-cursor{bottom:0;cursor:default;position:absolute;top:0;width:2px}.tox-rtc-user-cursor::before{background-color:inherit;border-radius:50%;content:'';display:block;height:8px;position:absolute;right:-3px;top:-3px;width:8px}.tox-rtc-user-cursor:hover::after{background-color:inherit;border-radius:100px;box-sizing:border-box;color:#fff;content:attr(data-user);display:block;font-size:12px;font-weight:700;left:-5px;min-height:8px;min-width:8px;padding:0 12px;position:absolute;top:-11px;white-space:nowrap;z-index:1000}.tox-rtc-user-selection--1 .tox-rtc-user-cursor{background-color:#2dc26b}.tox-rtc-user-selection--2 .tox-rtc-user-cursor{background-color:#e03e2d}.tox-rtc-user-selection--3 .tox-rtc-user-cursor{background-color:#f1c40f}.tox-rtc-user-selection--4 .tox-rtc-user-cursor{background-color:#3598db}.tox-rtc-user-selection--5 .tox-rtc-user-cursor{background-color:#b96ad9}.tox-rtc-user-selection--6 .tox-rtc-user-cursor{background-color:#e67e23}.tox-rtc-user-selection--7 .tox-rtc-user-cursor{background-color:#aaa69d}.tox-rtc-user-selection--8 .tox-rtc-user-cursor{background-color:#f368e0}.tox-rtc-remote-image{background:#eaeaea url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D%2236%22%20height%3D%2212%22%20viewBox%3D%220%200%2036%2012%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%0A%20%20%3Ccircle%20cx%3D%226%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2218%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.33s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%20%20%3Ccircle%20cx%3D%2230%22%20cy%3D%226%22%20r%3D%223%22%20fill%3D%22rgba(0%2C%200%2C%200%2C%20.2)%22%3E%0A%20%20%20%20%3Canimate%20attributeName%3D%22r%22%20values%3D%223%3B5%3B3%22%20calcMode%3D%22linear%22%20begin%3D%22.66s%22%20dur%3D%221s%22%20repeatCount%3D%22indefinite%22%20%2F%3E%0A%20%20%3C%2Fcircle%3E%0A%3C%2Fsvg%3E%0A\") no-repeat center center;border:1px solid #ccc;min-height:240px;min-width:320px}.mce-match-marker{background:#aaa;color:#fff}.mce-match-marker-selected{background:#39f;color:#fff}.mce-match-marker-selected::-moz-selection{background:#39f;color:#fff}.mce-match-marker-selected::selection{background:#39f;color:#fff}.mce-content-body audio[data-mce-selected],.mce-content-body details[data-mce-selected],.mce-content-body embed[data-mce-selected],.mce-content-body img[data-mce-selected],.mce-content-body object[data-mce-selected],.mce-content-body table[data-mce-selected],.mce-content-body video[data-mce-selected]{outline:3px solid #4099ff}.mce-content-body hr[data-mce-selected]{outline:3px solid #4099ff;outline-offset:1px}.mce-content-body [contentEditable=false] [contentEditable=true]:focus{outline:3px solid #4099ff}.mce-content-body [contentEditable=false] [contentEditable=true]:hover{outline:3px solid #4099ff}.mce-content-body [contentEditable=false][data-mce-selected]{cursor:not-allowed;outline:3px solid #4099ff}.mce-content-body.mce-content-readonly [contentEditable=true]:focus,.mce-content-body.mce-content-readonly [contentEditable=true]:hover{outline:0}.mce-content-body [data-mce-selected=inline-boundary]{background-color:#4099ff}.mce-content-body .mce-edit-focus{outline:3px solid #4099ff}.mce-content-body td[data-mce-selected],.mce-content-body th[data-mce-selected]{position:relative}.mce-content-body td[data-mce-selected]::-moz-selection,.mce-content-body th[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body td[data-mce-selected]::selection,.mce-content-body th[data-mce-selected]::selection{background:0 0}.mce-content-body td[data-mce-selected] *,.mce-content-body th[data-mce-selected] *{outline:0;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{background-color:rgba(180,215,255,.7);border:1px solid transparent;bottom:-1px;content:'';left:-1px;mix-blend-mode:lighten;position:absolute;right:-1px;top:-1px}@media screen and (-ms-high-contrast:active),(-ms-high-contrast:none){.mce-content-body td[data-mce-selected]::after,.mce-content-body th[data-mce-selected]::after{border-color:rgba(0,84,180,.7)}}.mce-content-body img[data-mce-selected]::-moz-selection{background:0 0}.mce-content-body img[data-mce-selected]::selection{background:0 0}.ephox-snooker-resizer-bar{background-color:#4099ff;opacity:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.ephox-snooker-resizer-cols{cursor:col-resize}.ephox-snooker-resizer-rows{cursor:row-resize}.ephox-snooker-resizer-bar.ephox-snooker-resizer-bar-dragging{opacity:1}.mce-spellchecker-word{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%23ff0000'%20fill%3D'none'%20stroke-linecap%3D'round'%20stroke-opacity%3D'.75'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default;height:2rem}.mce-spellchecker-grammar{background-image:url(\"data:image/svg+xml;charset=UTF-8,%3Csvg%20width%3D'4'%20height%3D'4'%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%3E%3Cpath%20stroke%3D'%2300A835'%20fill%3D'none'%20stroke-linecap%3D'round'%20d%3D'M0%203L2%201%204%203'%2F%3E%3C%2Fsvg%3E%0A\");background-position:0 calc(100% + 1px);background-repeat:repeat-x;background-size:auto 6px;cursor:default}.mce-toc{border:1px solid gray}.mce-toc h2{margin:4px}.mce-toc ul>li{list-style-type:none}[data-mce-block]{display:block}.mce-item-table:not([border]),.mce-item-table:not([border]) caption,.mce-item-table:not([border]) td,.mce-item-table:not([border]) th,.mce-item-table[border=\"0\"],.mce-item-table[border=\"0\"] caption,.mce-item-table[border=\"0\"] td,.mce-item-table[border=\"0\"] th,table[style*=\"border-width: 0px\"],table[style*=\"border-width: 0px\"] caption,table[style*=\"border-width: 0px\"] td,table[style*=\"border-width: 0px\"] th{border:1px dashed #bbb}.mce-visualblocks address,.mce-visualblocks article,.mce-visualblocks aside,.mce-visualblocks blockquote,.mce-visualblocks div:not([data-mce-bogus]),.mce-visualblocks dl,.mce-visualblocks figcaption,.mce-visualblocks figure,.mce-visualblocks h1,.mce-visualblocks h2,.mce-visualblocks h3,.mce-visualblocks h4,.mce-visualblocks h5,.mce-visualblocks h6,.mce-visualblocks hgroup,.mce-visualblocks ol,.mce-visualblocks p,.mce-visualblocks pre,.mce-visualblocks section,.mce-visualblocks ul{background-repeat:no-repeat;border:1px dashed #bbb;margin-left:3px;padding-top:10px}.mce-visualblocks p{background-image:url(data:image/gif;base64,R0lGODlhCQAJAJEAAAAAAP///7u7u////yH5BAEAAAMALAAAAAAJAAkAAAIQnG+CqCN/mlyvsRUpThG6AgA7)}.mce-visualblocks h1{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGu1JuxHoAfRNRW3TWXyF2YiRUAOw==)}.mce-visualblocks h2{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8Hybbx4oOuqgTynJd6bGlWg3DkJzoaUAAAOw==)}.mce-visualblocks h3{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIZjI8Hybbx4oOuqgTynJf2Ln2NOHpQpmhAAQA7)}.mce-visualblocks h4{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxInR0zqeAdhtJlXwV1oCll2HaWgAAOw==)}.mce-visualblocks h5{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjane4iq5GlW05GgIkIZUAAAOw==)}.mce-visualblocks h6{background-image:url(data:image/gif;base64,R0lGODlhDgAKAIABALu7u////yH5BAEAAAEALAAAAAAOAAoAAAIajI8HybbxIoiuwjan04jep1iZ1XRlAo5bVgAAOw==)}.mce-visualblocks div:not([data-mce-bogus]){background-image:url(data:image/gif;base64,R0lGODlhEgAKAIABALu7u////yH5BAEAAAEALAAAAAASAAoAAAIfjI9poI0cgDywrhuxfbrzDEbQM2Ei5aRjmoySW4pAAQA7)}.mce-visualblocks section{background-image:url(data:image/gif;base64,R0lGODlhKAAKAIABALu7u////yH5BAEAAAEALAAAAAAoAAoAAAI5jI+pywcNY3sBWHdNrplytD2ellDeSVbp+GmWqaDqDMepc8t17Y4vBsK5hDyJMcI6KkuYU+jpjLoKADs=)}.mce-visualblocks article{background-image:url(data:image/gif;base64,R0lGODlhKgAKAIABALu7u////yH5BAEAAAEALAAAAAAqAAoAAAI6jI+pywkNY3wG0GBvrsd2tXGYSGnfiF7ikpXemTpOiJScasYoDJJrjsG9gkCJ0ag6KhmaIe3pjDYBBQA7)}.mce-visualblocks blockquote{background-image:url(data:image/gif;base64,R0lGODlhPgAKAIABALu7u////yH5BAEAAAEALAAAAAA+AAoAAAJPjI+py+0Knpz0xQDyuUhvfoGgIX5iSKZYgq5uNL5q69asZ8s5rrf0yZmpNkJZzFesBTu8TOlDVAabUyatguVhWduud3EyiUk45xhTTgMBBQA7)}.mce-visualblocks address{background-image:url(data:image/gif;base64,R0lGODlhLQAKAIABALu7u////yH5BAEAAAEALAAAAAAtAAoAAAI/jI+pywwNozSP1gDyyZcjb3UaRpXkWaXmZW4OqKLhBmLs+K263DkJK7OJeifh7FicKD9A1/IpGdKkyFpNmCkAADs=)}.mce-visualblocks pre{background-image:url(data:image/gif;base64,R0lGODlhFQAKAIABALu7uwAAACH5BAEAAAEALAAAAAAVAAoAAAIjjI+ZoN0cgDwSmnpz1NCueYERhnibZVKLNnbOq8IvKpJtVQAAOw==)}.mce-visualblocks figure{background-image:url(data:image/gif;base64,R0lGODlhJAAKAIAAALu7u////yH5BAEAAAEALAAAAAAkAAoAAAI0jI+py+2fwAHUSFvD3RlvG4HIp4nX5JFSpnZUJ6LlrM52OE7uSWosBHScgkSZj7dDKnWAAgA7)}.mce-visualblocks figcaption{border:1px dashed #bbb}.mce-visualblocks hgroup{background-image:url(data:image/gif;base64,R0lGODlhJwAKAIABALu7uwAAACH5BAEAAAEALAAAAAAnAAoAAAI3jI+pywYNI3uB0gpsRtt5fFnfNZaVSYJil4Wo03Hv6Z62uOCgiXH1kZIIJ8NiIxRrAZNMZAtQAAA7)}.mce-visualblocks aside{background-image:url(data:image/gif;base64,R0lGODlhHgAKAIABAKqqqv///yH5BAEAAAEALAAAAAAeAAoAAAItjI+pG8APjZOTzgtqy7I3f1yehmQcFY4WKZbqByutmW4aHUd6vfcVbgudgpYCADs=)}.mce-visualblocks ul{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIAAALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybGuYnqUVSjvw26DzzXiqIDlVwAAOw==)}.mce-visualblocks ol{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybH6HHt0qourxC6CvzXieHyeWQAAOw==)}.mce-visualblocks dl{background-image:url(data:image/gif;base64,R0lGODlhDQAKAIABALu7u////yH5BAEAAAEALAAAAAANAAoAAAIXjI8GybEOnmOvUoWznTqeuEjNSCqeGRUAOw==)}.mce-visualblocks:not([dir=rtl]) address,.mce-visualblocks:not([dir=rtl]) article,.mce-visualblocks:not([dir=rtl]) aside,.mce-visualblocks:not([dir=rtl]) blockquote,.mce-visualblocks:not([dir=rtl]) div:not([data-mce-bogus]),.mce-visualblocks:not([dir=rtl]) dl,.mce-visualblocks:not([dir=rtl]) figcaption,.mce-visualblocks:not([dir=rtl]) figure,.mce-visualblocks:not([dir=rtl]) h1,.mce-visualblocks:not([dir=rtl]) h2,.mce-visualblocks:not([dir=rtl]) h3,.mce-visualblocks:not([dir=rtl]) h4,.mce-visualblocks:not([dir=rtl]) h5,.mce-visualblocks:not([dir=rtl]) h6,.mce-visualblocks:not([dir=rtl]) hgroup,.mce-visualblocks:not([dir=rtl]) ol,.mce-visualblocks:not([dir=rtl]) p,.mce-visualblocks:not([dir=rtl]) pre,.mce-visualblocks:not([dir=rtl]) section,.mce-visualblocks:not([dir=rtl]) ul{margin-left:3px}.mce-visualblocks[dir=rtl] address,.mce-visualblocks[dir=rtl] article,.mce-visualblocks[dir=rtl] aside,.mce-visualblocks[dir=rtl] blockquote,.mce-visualblocks[dir=rtl] div:not([data-mce-bogus]),.mce-visualblocks[dir=rtl] dl,.mce-visualblocks[dir=rtl] figcaption,.mce-visualblocks[dir=rtl] figure,.mce-visualblocks[dir=rtl] h1,.mce-visualblocks[dir=rtl] h2,.mce-visualblocks[dir=rtl] h3,.mce-visualblocks[dir=rtl] h4,.mce-visualblocks[dir=rtl] h5,.mce-visualblocks[dir=rtl] h6,.mce-visualblocks[dir=rtl] hgroup,.mce-visualblocks[dir=rtl] ol,.mce-visualblocks[dir=rtl] p,.mce-visualblocks[dir=rtl] pre,.mce-visualblocks[dir=rtl] section,.mce-visualblocks[dir=rtl] ul{background-position-x:right;margin-right:3px}.mce-nbsp,.mce-shy{background:#aaa}.mce-shy::after{content:'-'}body{font-family:sans-serif}table{border-collapse:collapse}") //# sourceMappingURL=content.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5-dark/skin.js ================================================ tinymce.Resource.add('ui/tinymce-5-dark/skin.css', ".tox{box-shadow:none;box-sizing:content-box;color:#2a3746;cursor:auto;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-style:normal;font-weight:400;line-height:normal;-webkit-tap-highlight-color:transparent;text-decoration:none;text-shadow:none;text-transform:none;vertical-align:initial;white-space:normal}.tox :not(svg):not(rect){box-sizing:inherit;color:inherit;cursor:inherit;direction:inherit;font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;line-height:inherit;-webkit-tap-highlight-color:inherit;text-align:inherit;text-decoration:inherit;text-shadow:inherit;text-transform:inherit;vertical-align:inherit;white-space:inherit}.tox :not(svg):not(rect){background:0 0;border:0;box-shadow:none;float:none;height:auto;margin:0;max-width:none;outline:0;padding:0;position:static;width:auto}.tox:not([dir=rtl]){direction:ltr;text-align:left}.tox[dir=rtl]{direction:rtl;text-align:right}.tox-tinymce{border:1px solid #000;border-radius:0;box-shadow:none;box-sizing:border-box;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;overflow:hidden;position:relative;visibility:inherit!important}.tox.tox-tinymce-inline{border:none;box-shadow:none;overflow:initial}.tox.tox-tinymce-inline .tox-editor-container{overflow:initial}.tox.tox-tinymce-inline .tox-editor-header{background-color:#222f3e;border:1px solid #000;border-radius:0;box-shadow:none;overflow:hidden}.tox-tinymce-aux{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;z-index:1300}.tox-tinymce :focus,.tox-tinymce-aux :focus{outline:0}button::-moz-focus-inner{border:0}.tox[dir=rtl] .tox-icon--flip svg{transform:rotateY(180deg)}.tox .accessibility-issue__header{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description{align-items:stretch;border-radius:3px;display:flex;justify-content:space-between}.tox .accessibility-issue__description>div{padding-bottom:4px}.tox .accessibility-issue__description>div>div{align-items:center;display:flex;margin-bottom:4px}.tox .accessibility-issue__description>div>div .tox-icon svg{display:block}.tox .accessibility-issue__repair{margin-top:16px}.tox .tox-dialog__body-content .accessibility-issue--info .accessibility-issue__description{background-color:rgba(30,113,170,.4);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon{background-color:#207ab7;color:#fff}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:hover{background-color:#1c6ca1}.tox .tox-dialog__body-content .accessibility-issue--info a.tox-button--naked.tox-button--icon:active{background-color:#185d8c}.tox .tox-dialog__body-content .accessibility-issue--warn .accessibility-issue__description{background-color:rgba(255,165,0,.5);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon{background-color:#ffe89d;color:#2a3746}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:hover{background-color:#f2d574;color:#2a3746}.tox .tox-dialog__body-content .accessibility-issue--warn a.tox-button--naked.tox-button--icon:active{background-color:#e8c657;color:#2a3746}.tox .tox-dialog__body-content .accessibility-issue--error .accessibility-issue__description{background-color:rgba(204,0,0,.5);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--error .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--error .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon{background-color:#f2bfbf;color:#2a3746}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:focus,.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:hover{background-color:#e9a4a4;color:#2a3746}.tox .tox-dialog__body-content .accessibility-issue--error a.tox-button--naked.tox-button--icon:active{background-color:#ee9494;color:#2a3746}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description{background-color:rgba(120,171,70,.5);color:#fff}.tox .tox-dialog__body-content .accessibility-issue--success .accessibility-issue__description>:last-child{display:none}.tox .tox-dialog__body-content .accessibility-issue--success .tox-form__group h2{color:#fff}.tox .tox-dialog__body-content .accessibility-issue--success .tox-icon svg{fill:#fff}.tox .tox-dialog__body-content .accessibility-issue__header .tox-form__group h1,.tox .tox-dialog__body-content .tox-form__group .accessibility-issue__description h2{font-size:14px;margin-top:0}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-left:4px}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-left:auto}.tox:not([dir=rtl]) .tox-dialog__body-content .accessibility-issue__description{padding:4px 4px 4px 8px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header .tox-button{margin-right:4px}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__header>:nth-last-child(2){margin-right:auto}.tox[dir=rtl] .tox-dialog__body-content .accessibility-issue__description{padding:4px 8px 4px 4px}.tox .mce-codemirror{background:#fff;bottom:0;font-size:13px;left:0;position:absolute;right:0;top:0;z-index:1}.tox .mce-codemirror.tox-inline-codemirror{margin:8px;position:absolute}.tox .tox-advtemplate .tox-form__grid{flex:1}.tox .tox-advtemplate .tox-form__grid>div:first-child{display:flex;flex-direction:column;width:30%}.tox .tox-advtemplate .tox-form__grid>div:first-child>div:nth-child(2){flex-basis:0;flex-grow:1;overflow:auto}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-advtemplate .tox-form__grid>div:first-child{width:100%}}.tox .tox-advtemplate iframe{border-color:#000;border-radius:0;border-style:solid;border-width:1px;margin:0 10px}.tox .tox-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bottom-anchorbar{display:flex;flex:0 0 auto}.tox .tox-bar{display:flex;flex:0 0 auto}.tox .tox-button{background-color:#207ab7;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#207ab7;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;line-height:24px;margin:0;outline:0;padding:4px 16px;position:relative;text-align:center;text-decoration:none;text-transform:none;white-space:nowrap}.tox .tox-button::before{border-radius:3px;bottom:-1px;box-shadow:inset 0 0 0 1px #fff,0 0 0 2px #207ab7;content:'';left:-1px;opacity:0;pointer-events:none;position:absolute;right:-1px;top:-1px}.tox .tox-button[disabled]{background-color:#207ab7;background-image:none;border-color:#207ab7;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button:focus:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:focus:not(:disabled)::before{opacity:1}.tox .tox-button:hover:not(:disabled){background-color:#1c6ca1;background-image:none;border-color:#1c6ca1;box-shadow:none;color:#fff}.tox .tox-button:active:not(:disabled){background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled{background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled[disabled]{background-color:#185d8c;background-image:none;border-color:#185d8c;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-button.tox-button--enabled:focus:not(:disabled){background-color:#154f76;background-image:none;border-color:#154f76;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:hover:not(:disabled){background-color:#154f76;background-image:none;border-color:#154f76;box-shadow:none;color:#fff}.tox .tox-button.tox-button--enabled:active:not(:disabled){background-color:#114060;background-image:none;border-color:#114060;box-shadow:none;color:#fff}.tox .tox-button--icon-and-text,.tox .tox-button.tox-button--icon-and-text,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text{display:flex;padding:5px 4px}.tox .tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--icon-and-text .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon-and-text .tox-icon svg{display:block;fill:currentColor}.tox .tox-button--secondary{background-color:#3d546f;background-image:none;background-position:0 0;background-repeat:repeat;border-color:#3d546f;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;color:#fff;font-size:14px;font-style:normal;font-weight:700;letter-spacing:normal;outline:0;padding:4px 16px;text-decoration:none;text-transform:none}.tox .tox-button--secondary[disabled]{background-color:#3d546f;background-image:none;border-color:#3d546f;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-button--secondary:focus:not(:disabled){background-color:#34485f;background-image:none;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--secondary:hover:not(:disabled){background-color:#34485f;background-image:none;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--secondary:active:not(:disabled){background-color:#2b3b4e;background-image:none;border-color:#2b3b4e;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled{background-color:#346085;background-image:none;border-color:#346085;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled[disabled]{background-color:#346085;background-image:none;border-color:#346085;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-button--secondary.tox-button--enabled:focus:not(:disabled){background-color:#2d5373;background-image:none;border-color:#2d5373;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled:hover:not(:disabled){background-color:#2d5373;background-image:none;border-color:#2d5373;box-shadow:none;color:#fff}.tox .tox-button--secondary.tox-button--enabled:active:not(:disabled){background-color:#264560;background-image:none;border-color:#264560;box-shadow:none;color:#fff}.tox .tox-button--icon,.tox .tox-button.tox-button--icon,.tox .tox-button.tox-button--secondary.tox-button--icon{padding:4px}.tox .tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--icon .tox-icon svg,.tox .tox-button.tox-button--secondary.tox-button--icon .tox-icon svg{display:block;fill:currentColor}.tox .tox-button-link{background:0;border:none;box-sizing:border-box;cursor:pointer;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;white-space:nowrap}.tox .tox-button-link--sm{font-size:14px}.tox .tox-button--naked{background-color:transparent;border-color:transparent;box-shadow:unset;color:#fff}.tox .tox-button--naked[disabled]{background-color:#3d546f;border-color:#3d546f;box-shadow:none;color:rgba(255,255,255,.5)}.tox .tox-button--naked:hover:not(:disabled){background-color:#34485f;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--naked:focus:not(:disabled){background-color:#34485f;border-color:#34485f;box-shadow:none;color:#fff}.tox .tox-button--naked:active:not(:disabled){background-color:#2b3b4e;border-color:#2b3b4e;box-shadow:none;color:#fff}.tox .tox-button--naked .tox-icon svg{fill:currentColor}.tox .tox-button--naked.tox-button--icon:hover:not(:disabled){color:#fff}.tox .tox-checkbox{align-items:center;border-radius:3px;cursor:pointer;display:flex;height:36px;min-width:36px}.tox .tox-checkbox__input{height:1px;overflow:hidden;position:absolute;top:auto;width:1px}.tox .tox-checkbox__icons{align-items:center;border-radius:3px;box-shadow:0 0 0 2px transparent;box-sizing:content-box;display:flex;height:24px;justify-content:center;padding:calc(4px - 1px);width:24px}.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:block;fill:rgba(255,255,255,.2)}@media (forced-colors:active){.tox .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:currentColor!important}}.tox .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:none;fill:#207ab7}.tox .tox-checkbox__icons .tox-checkbox-icon__checked svg{display:none;fill:#207ab7}.tox .tox-checkbox--disabled{color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__checked svg{fill:rgba(255,255,255,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__unchecked svg{fill:rgba(255,255,255,.5)}.tox .tox-checkbox--disabled .tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{fill:rgba(255,255,255,.5)}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:checked+.tox-checkbox__icons .tox-checkbox-icon__checked svg{display:block}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__unchecked svg{display:none}.tox input.tox-checkbox__input:indeterminate+.tox-checkbox__icons .tox-checkbox-icon__indeterminate svg{display:block}.tox input.tox-checkbox__input:focus+.tox-checkbox__icons{border-radius:3px;box-shadow:inset 0 0 0 1px #207ab7;padding:calc(4px - 1px)}.tox:not([dir=rtl]) .tox-checkbox__label{margin-left:4px}.tox:not([dir=rtl]) .tox-checkbox__input{left:-10000px}.tox:not([dir=rtl]) .tox-bar .tox-checkbox{margin-left:4px}.tox[dir=rtl] .tox-checkbox__label{margin-right:4px}.tox[dir=rtl] .tox-checkbox__input{right:-10000px}.tox[dir=rtl] .tox-bar .tox-checkbox{margin-right:4px}.tox .tox-collection--toolbar .tox-collection__group{display:flex;padding:0}.tox .tox-collection--grid .tox-collection__group{display:flex;flex-wrap:wrap;max-height:208px;overflow-x:hidden;overflow-y:auto;padding:0}.tox .tox-collection--list .tox-collection__group{border-bottom-width:0;border-color:#1a1a1a;border-left-width:0;border-right-width:0;border-style:solid;border-top-width:1px;padding:4px 0}.tox .tox-collection--list .tox-collection__group:first-child{border-top-width:0}.tox .tox-collection__group-heading{background-color:#333;color:#fff;cursor:default;font-size:12px;font-style:normal;font-weight:400;margin-bottom:4px;margin-top:-4px;padding:4px 8px;text-transform:none;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection__item{align-items:center;border-radius:3px;color:#fff;display:flex;-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.tox .tox-collection--list .tox-collection__item{padding:4px 8px}.tox .tox-collection--toolbar .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--grid .tox-collection__item{border-radius:3px;padding:4px}.tox .tox-collection--list .tox-collection__item--enabled{background-color:#2b3b4e;color:#fff}.tox .tox-collection--list .tox-collection__item--active{background-color:#4a5562}.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{background-color:#757d87;color:#fff}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--enabled,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active,.tox .tox-collection--toolbar .tox-collection__item--enabled.tox-collection__item--active:hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active{background-color:#2b3b4e;position:relative}.tox .tox-collection--toolbar .tox-collection__item--active:hover{background-color:#4a5562;color:#fff}.tox .tox-collection--toolbar .tox-collection__item--active:focus{background-color:#4a5562;color:#fff}.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:focus::after{border:2px solid highlight}}.tox .tox-collection--grid .tox-collection__item--enabled{background-color:#757d87;color:#fff}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled){background-color:#4a5562;color:#fff;position:relative;z-index:1}.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent inset;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-collection--grid .tox-collection__item--active:not(.tox-collection__item--state-disabled):focus::after{border:2px solid highlight}}.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#fff}@media (forced-colors:active){.tox .tox-collection--list .tox-collection__item--active:not(.tox-collection__item--state-disabled){border:solid 1px}}.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled){color:#fff}@media (forced-colors:active){.tox .tox-collection--toolbar .tox-collection__item--active:not(.tox-collection__item--state-disabled):hover{border-radius:3px;outline:solid 1px}}.tox .tox-collection__item-checkmark,.tox .tox-collection__item-icon{align-items:center;display:flex;height:24px;justify-content:center;width:24px}.tox .tox-collection__item-checkmark svg,.tox .tox-collection__item-icon svg{fill:currentColor}.tox .tox-collection--toolbar-lg .tox-collection__item-icon{height:48px;width:48px}.tox .tox-collection__item-label{color:currentColor;display:inline-block;flex:1;font-size:14px;font-style:normal;font-weight:400;line-height:24px;max-width:100%;text-transform:none;word-break:break-all}.tox .tox-collection__item-accessory{color:currentColor;display:inline-block;font-size:14px;height:24px;line-height:24px;text-transform:none}.tox .tox-collection__item-caret{align-items:center;display:flex;min-height:24px}.tox .tox-collection__item-caret::after{content:'';font-size:0;min-height:inherit}.tox .tox-collection__item-caret svg{fill:currentColor}.tox .tox-collection__item--state-disabled{background-color:transparent;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-collection__item--state-disabled .tox-collection__item-caret svg{fill:rgba(255,255,255,.5)}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-checkmark svg{display:none}.tox .tox-collection--list .tox-collection__item:not(.tox-collection__item--enabled) .tox-collection__item-accessory+.tox-collection__item-checkmark{display:none}.tox .tox-collection--horizontal{background-color:#2b3b4e;border:1px solid #1a1a1a;border-radius:3px;box-shadow:0 0 2px 0 rgba(42,55,70,.2),0 4px 8px 0 rgba(42,55,70,.15);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:nowrap;margin-bottom:0;overflow-x:auto;padding:0}.tox .tox-collection--horizontal .tox-collection__group{align-items:center;display:flex;flex-wrap:nowrap;margin:0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item{height:34px;margin:3px 0 2px 0;padding:0 4px}.tox .tox-collection--horizontal .tox-collection__item-label{white-space:nowrap}.tox .tox-collection--horizontal .tox-collection__item-caret{margin-left:4px}.tox .tox-collection__item-container{display:flex}.tox .tox-collection__item-container--row{align-items:center;flex:1 1 auto;flex-direction:row}.tox .tox-collection__item-container--row.tox-collection__item-container--align-left{margin-right:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--align-right{justify-content:flex-end;margin-left:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-top{align-items:flex-start;margin-bottom:auto}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-middle{align-items:center}.tox .tox-collection__item-container--row.tox-collection__item-container--valign-bottom{align-items:flex-end;margin-top:auto}.tox .tox-collection__item-container--column{align-self:center;flex:1 1 auto;flex-direction:column}.tox .tox-collection__item-container--column.tox-collection__item-container--align-left{align-items:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--align-right{align-items:flex-end}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-top{align-self:flex-start}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-middle{align-self:center}.tox .tox-collection__item-container--column.tox-collection__item-container--valign-bottom{align-self:flex-end}.tox:not([dir=rtl]) .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-right:1px solid #000}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>:not(:first-child){margin-left:8px}.tox:not([dir=rtl]) .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-left:4px}.tox:not([dir=rtl]) .tox-collection__item-accessory{margin-left:16px;text-align:right}.tox:not([dir=rtl]) .tox-collection .tox-collection__item-caret{margin-left:16px}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__group:not(:last-of-type){border-left:1px solid #000}.tox[dir=rtl] .tox-collection--list .tox-collection__item>:not(:first-child){margin-right:8px}.tox[dir=rtl] .tox-collection--list .tox-collection__item>.tox-collection__item-label:first-child{margin-right:4px}.tox[dir=rtl] .tox-collection__item-accessory{margin-right:16px;text-align:left}.tox[dir=rtl] .tox-collection .tox-collection__item-caret{margin-right:16px;transform:rotateY(180deg)}.tox[dir=rtl] .tox-collection--horizontal .tox-collection__item-caret{margin-right:4px}@media (forced-colors:active){.tox .tox-hue-slider,.tox .tox-rgb-form .tox-rgba-preview{background-color:currentColor!important;border:1px solid highlight!important;forced-color-adjust:none}}.tox .tox-color-picker-container{display:flex;flex-direction:row;height:225px;margin:0}.tox .tox-sv-palette{box-sizing:border-box;display:flex;height:100%}.tox .tox-sv-palette-spectrum{height:100%}.tox .tox-sv-palette,.tox .tox-sv-palette-spectrum{width:225px}.tox .tox-sv-palette-thumb{background:0 0;border:1px solid #000;border-radius:50%;box-sizing:content-box;height:12px;position:absolute;width:12px}.tox .tox-sv-palette-inner-thumb{border:1px solid #fff;border-radius:50%;height:10px;position:absolute;width:10px}.tox .tox-hue-slider{box-sizing:border-box;height:100%;width:25px}.tox .tox-hue-slider-spectrum{background:linear-gradient(to bottom,red,#ff0080,#f0f,#8000ff,#00f,#0080ff,#0ff,#00ff80,#0f0,#80ff00,#ff0,#ff8000,red);height:100%;width:100%}.tox .tox-hue-slider,.tox .tox-hue-slider-spectrum{width:20px}.tox .tox-hue-slider-spectrum:focus,.tox .tox-sv-palette-spectrum:focus{outline:#08f solid}.tox .tox-hue-slider-thumb{background:#fff;border:1px solid #000;box-sizing:content-box;height:4px;width:100%}.tox .tox-rgb-form{display:flex;flex-direction:column;justify-content:space-between}.tox .tox-rgb-form div{align-items:center;display:flex;justify-content:space-between;margin-bottom:5px;width:inherit}.tox .tox-rgb-form input{width:6em}.tox .tox-rgb-form input.tox-invalid{border:1px solid red!important}.tox .tox-rgb-form .tox-rgba-preview{border:1px solid #000;flex-grow:2;margin-bottom:0}.tox:not([dir=rtl]) .tox-sv-palette{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider{margin-right:15px}.tox:not([dir=rtl]) .tox-hue-slider-thumb{margin-left:-1px}.tox:not([dir=rtl]) .tox-rgb-form label{margin-right:.5em}.tox[dir=rtl] .tox-sv-palette{margin-left:15px}.tox[dir=rtl] .tox-hue-slider{margin-left:15px}.tox[dir=rtl] .tox-hue-slider-thumb{margin-right:-1px}.tox[dir=rtl] .tox-rgb-form label{margin-left:.5em}.tox .tox-toolbar .tox-swatches,.tox .tox-toolbar__overflow .tox-swatches,.tox .tox-toolbar__primary .tox-swatches{margin:2px 0 3px 4px}.tox .tox-collection--list .tox-collection__group .tox-swatches-menu{border:0;margin:-4px 0}.tox .tox-swatches__row{display:flex}@media (forced-colors:active){.tox .tox-swatches__row{forced-color-adjust:none}}.tox .tox-swatch{height:30px;transition:transform .15s,box-shadow .15s;width:30px}.tox .tox-swatch:focus,.tox .tox-swatch:hover{box-shadow:0 0 0 1px rgba(127,127,127,.3) inset;transform:scale(.8)}.tox .tox-swatch--remove{align-items:center;display:flex;justify-content:center}.tox .tox-swatch--remove svg path{stroke:#e74c3c}.tox .tox-swatches__picker-btn{align-items:center;background-color:transparent;border:0;cursor:pointer;display:flex;height:30px;justify-content:center;outline:0;padding:0;width:30px}.tox .tox-swatches__picker-btn svg{fill:#fff;height:24px;width:24px}.tox .tox-swatches__picker-btn:hover{background:#4a5562}.tox div.tox-swatch:not(.tox-swatch--remove) svg{display:none;fill:#fff;height:24px;margin:calc((30px - 24px)/ 2) calc((30px - 24px)/ 2);width:24px}.tox div.tox-swatch:not(.tox-swatch--remove) svg path{fill:#fff;paint-order:stroke;stroke:#222f3e;stroke-width:2px}.tox div.tox-swatch:not(.tox-swatch--remove).tox-collection__item--enabled svg{display:block}.tox:not([dir=rtl]) .tox-swatches__picker-btn{margin-left:auto}.tox[dir=rtl] .tox-swatches__picker-btn{margin-right:auto}.tox .tox-comment-thread{background:#2b3b4e;position:relative}.tox .tox-comment-thread>:not(:first-child){margin-top:8px}.tox .tox-comment{background:#2b3b4e;border:1px solid #000;border-radius:3px;box-shadow:0 4px 8px 0 rgba(42,55,70,.1);padding:8px 8px 16px 8px;position:relative}.tox .tox-comment__header{align-items:center;color:#fff;display:flex;justify-content:space-between}.tox .tox-comment__date{color:#fff;font-size:12px;line-height:18px}.tox .tox-comment__body{color:#fff;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;margin-top:8px;position:relative;text-transform:initial}.tox .tox-comment__body textarea{resize:none;white-space:normal;width:100%}.tox .tox-comment__expander{padding-top:8px}.tox .tox-comment__expander p{color:rgba(255,255,255,.5);font-size:14px;font-style:normal}.tox .tox-comment__body p{margin:0}.tox .tox-comment__buttonspacing{padding-top:16px;text-align:center}.tox .tox-comment-thread__overlay::after{background:#2b3b4e;bottom:0;content:\"\";display:flex;left:0;opacity:.9;position:absolute;right:0;top:0;z-index:5}.tox .tox-comment__reply{display:flex;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;margin-top:8px}.tox .tox-comment__reply>:first-child{margin-bottom:8px;width:100%}.tox .tox-comment__edit{display:flex;flex-wrap:wrap;justify-content:flex-end;margin-top:16px}.tox .tox-comment__gradient::after{background:linear-gradient(rgba(43,59,78,0),#2b3b4e);bottom:0;content:\"\";display:block;height:5em;margin-top:-40px;position:absolute;width:100%}.tox .tox-comment__overlay{background:#2b3b4e;bottom:0;display:flex;flex-direction:column;flex-grow:1;left:0;opacity:.9;position:absolute;right:0;text-align:center;top:0;z-index:5}.tox .tox-comment__loading-text{align-items:center;color:#fff;display:flex;flex-direction:column;position:relative}.tox .tox-comment__loading-text>div{padding-bottom:16px}.tox .tox-comment__overlaytext{bottom:0;flex-direction:column;font-size:14px;left:0;padding:1em;position:absolute;right:0;top:0;z-index:10}.tox .tox-comment__overlaytext p{background-color:#2b3b4e;box-shadow:0 0 8px 8px #2b3b4e;color:#fff;text-align:center}.tox .tox-comment__overlaytext div:nth-of-type(2){font-size:.8em}.tox .tox-comment__busy-spinner{align-items:center;background-color:#2b3b4e;bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:20}.tox .tox-comment__scroll{display:flex;flex-direction:column;flex-shrink:1;overflow:auto}.tox .tox-conversations{margin:8px}.tox:not([dir=rtl]) .tox-comment__edit{margin-left:8px}.tox:not([dir=rtl]) .tox-comment__buttonspacing>:last-child,.tox:not([dir=rtl]) .tox-comment__edit>:last-child,.tox:not([dir=rtl]) .tox-comment__reply>:last-child{margin-left:8px}.tox[dir=rtl] .tox-comment__edit{margin-right:8px}.tox[dir=rtl] .tox-comment__buttonspacing>:last-child,.tox[dir=rtl] .tox-comment__edit>:last-child,.tox[dir=rtl] .tox-comment__reply>:last-child{margin-right:8px}.tox .tox-user{align-items:center;display:flex}.tox .tox-user__avatar svg{fill:rgba(255,255,255,.5)}.tox .tox-user__avatar img{border-radius:50%;height:36px;object-fit:cover;vertical-align:middle;width:36px}.tox .tox-user__name{color:#fff;font-size:14px;font-style:normal;font-weight:700;line-height:18px;text-transform:none}.tox:not([dir=rtl]) .tox-user__avatar img,.tox:not([dir=rtl]) .tox-user__avatar svg{margin-right:8px}.tox:not([dir=rtl]) .tox-user__avatar+.tox-user__name{margin-left:8px}.tox[dir=rtl] .tox-user__avatar img,.tox[dir=rtl] .tox-user__avatar svg{margin-left:8px}.tox[dir=rtl] .tox-user__avatar+.tox-user__name{margin-right:8px}.tox .tox-dialog-wrap{align-items:center;bottom:0;display:flex;justify-content:center;left:0;position:fixed;right:0;top:0;z-index:1100}.tox .tox-dialog-wrap__backdrop{background-color:rgba(34,47,62,.75);bottom:0;left:0;position:absolute;right:0;top:0;z-index:1}.tox .tox-dialog-wrap__backdrop--opaque{background-color:#222f3e}.tox .tox-dialog{background-color:#2b3b4e;border-color:#000;border-radius:3px;border-style:solid;border-width:1px;box-shadow:0 16px 16px -10px rgba(42,55,70,.15),0 0 40px 1px rgba(42,55,70,.15);display:flex;flex-direction:column;max-height:100%;max-width:480px;overflow:hidden;position:relative;width:95vw;z-index:2}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog{align-self:flex-start;margin:8px auto;max-height:calc(100vh - 8px * 2);width:calc(100vw - 16px)}}.tox .tox-dialog-inline{z-index:1100}.tox .tox-dialog__header{align-items:center;background-color:#2b3b4e;border-bottom:none;color:#fff;display:flex;font-size:16px;justify-content:space-between;padding:8px 16px 0 16px;position:relative}.tox .tox-dialog__header .tox-button{z-index:1}.tox .tox-dialog__draghandle{cursor:grab;height:100%;left:0;position:absolute;top:0;width:100%}.tox .tox-dialog__draghandle:active{cursor:grabbing}.tox .tox-dialog__dismiss{margin-left:auto}.tox .tox-dialog__title{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:20px;font-style:normal;font-weight:400;line-height:1.3;margin:0;text-transform:none}.tox .tox-dialog__body{color:#fff;display:flex;flex:1;font-size:16px;font-style:normal;font-weight:400;line-height:1.3;min-width:0;text-align:left;text-transform:none}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body{flex-direction:column}}.tox .tox-dialog__body-nav{align-items:flex-start;display:flex;flex-direction:column;flex-shrink:0;padding:16px 16px}@media only screen and (min-width:768px){.tox .tox-dialog__body-nav{max-width:11em}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox .tox-dialog__body-nav{flex-direction:row;-webkit-overflow-scrolling:touch;overflow-x:auto;padding-bottom:0}}.tox .tox-dialog__body-nav-item{border-bottom:2px solid transparent;color:rgba(255,255,255,.5);display:inline-block;flex-shrink:0;font-size:14px;line-height:1.3;margin-bottom:8px;max-width:13em;text-decoration:none}.tox .tox-dialog__body-nav-item:focus{background-color:rgba(32,122,183,.1)}.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid #207ab7;color:#207ab7}@media (forced-colors:active){.tox .tox-dialog__body-nav-item--active{border-bottom:2px solid highlight;color:highlight}}.tox .tox-dialog__body-content{box-sizing:border-box;display:flex;flex:1;flex-direction:column;max-height:min(650px,calc(100vh - 110px));overflow:auto;-webkit-overflow-scrolling:touch;padding:16px 16px}.tox .tox-dialog__body-content>*{margin-bottom:0;margin-top:16px}.tox .tox-dialog__body-content>:first-child{margin-top:0}.tox .tox-dialog__body-content>:last-child{margin-bottom:0}.tox .tox-dialog__body-content>:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content a{color:#207ab7;cursor:pointer;text-decoration:underline}.tox .tox-dialog__body-content a:focus,.tox .tox-dialog__body-content a:hover{color:#114060;text-decoration:underline}.tox .tox-dialog__body-content a:focus-visible{border-radius:1px;outline:2px solid #207ab7;outline-offset:2px}.tox .tox-dialog__body-content a:active{color:#092335;text-decoration:underline}.tox .tox-dialog__body-content svg{fill:#fff}.tox .tox-dialog__body-content strong{font-weight:700}.tox .tox-dialog__body-content ul{list-style-type:disc}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{padding-inline-start:2.5rem}.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{margin-bottom:16px}.tox .tox-dialog__body-content dd,.tox .tox-dialog__body-content dl,.tox .tox-dialog__body-content dt,.tox .tox-dialog__body-content ol,.tox .tox-dialog__body-content ul{display:block;margin-inline-end:0;margin-inline-start:0}.tox .tox-dialog__body-content .tox-form__group h1{color:#fff;font-size:20px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group h2{color:#fff;font-size:16px;font-style:normal;font-weight:700;letter-spacing:normal;margin-bottom:16px;margin-top:2rem;text-transform:none}.tox .tox-dialog__body-content .tox-form__group p{margin-bottom:16px}.tox .tox-dialog__body-content .tox-form__group h1:first-child,.tox .tox-dialog__body-content .tox-form__group h2:first-child,.tox .tox-dialog__body-content .tox-form__group p:first-child{margin-top:0}.tox .tox-dialog__body-content .tox-form__group h1:last-child,.tox .tox-dialog__body-content .tox-form__group h2:last-child,.tox .tox-dialog__body-content .tox-form__group p:last-child{margin-bottom:0}.tox .tox-dialog__body-content .tox-form__group h1:only-child,.tox .tox-dialog__body-content .tox-form__group h2:only-child,.tox .tox-dialog__body-content .tox-form__group p:only-child{margin-bottom:0;margin-top:0}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--center{text-align:center}.tox .tox-dialog__body-content .tox-form__group .tox-label.tox-label--end{text-align:end}.tox .tox-dialog--width-lg{height:650px;max-width:1200px}.tox .tox-dialog--fullscreen{height:100%;max-width:100%}.tox .tox-dialog--fullscreen .tox-dialog__body-content{max-height:100%}.tox .tox-dialog--width-md{max-width:800px}.tox .tox-dialog--width-md .tox-dialog__body-content{overflow:auto}.tox .tox-dialog__body-content--centered{text-align:center}.tox .tox-dialog__footer{align-items:center;background-color:#2b3b4e;border-top:1px solid #000;display:flex;justify-content:space-between;padding:8px 16px}.tox .tox-dialog__footer-end,.tox .tox-dialog__footer-start{display:flex}.tox .tox-dialog__busy-spinner{align-items:center;background-color:rgba(34,47,62,.75);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0;z-index:3}.tox .tox-dialog__table{border-collapse:collapse;width:100%}.tox .tox-dialog__table thead th{font-weight:700;padding-bottom:8px}.tox .tox-dialog__table thead th:first-child{padding-right:8px}.tox .tox-dialog__table tbody tr{border-bottom:1px solid #000}.tox .tox-dialog__table tbody tr:last-child{border-bottom:none}.tox .tox-dialog__table td{padding-bottom:8px;padding-top:8px}.tox .tox-dialog__table td:first-child{padding-right:8px}.tox .tox-dialog__iframe{min-height:200px}.tox .tox-dialog__iframe.tox-dialog__iframe--opaque{background:#fff}.tox .tox-navobj-bordered{position:relative}.tox .tox-navobj-bordered::before{border:1px solid #000;border-radius:3px;content:'';inset:0;opacity:1;pointer-events:none;position:absolute;z-index:1}.tox .tox-navobj-bordered iframe{border-radius:3px}.tox .tox-navobj-bordered-focus.tox-navobj-bordered::before{border-color:#207ab7;box-shadow:none;outline:2px solid rgba(32,122,183,.25)}.tox .tox-dialog__popups{position:absolute;width:100%;z-index:1100}.tox .tox-dialog__body-iframe{display:flex;flex:1;flex-direction:column}.tox .tox-dialog__body-iframe .tox-navobj{display:flex;flex:1}.tox .tox-dialog__body-iframe .tox-navobj :nth-child(2){flex:1;height:100%}.tox .tox-dialog-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-dialog-dock-fadein{opacity:1;visibility:visible}.tox .tox-dialog-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-dialog-dock-transition.tox-dialog-dock-fadein{transition-delay:0s}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav{margin-right:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox:not([dir=rtl]) .tox-dialog__body-nav-item:not(:first-child){margin-left:8px}}.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-end>*,.tox:not([dir=rtl]) .tox-dialog__footer .tox-dialog__footer-start>*{margin-left:8px}.tox[dir=rtl] .tox-dialog__body{text-align:right}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav{margin-left:0}}@media only screen and (max-width:767px){body:not(.tox-force-desktop) .tox[dir=rtl] .tox-dialog__body-nav-item:not(:first-child){margin-right:8px}}.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-end>*,.tox[dir=rtl] .tox-dialog__footer .tox-dialog__footer-start>*{margin-right:8px}body.tox-dialog__disable-scroll{overflow:hidden}.tox .tox-dropzone-container{display:flex;flex:1}.tox .tox-dropzone{align-items:center;background:#fff;border:2px dashed #000;box-sizing:border-box;display:flex;flex-direction:column;flex-grow:1;justify-content:center;min-height:100px;padding:10px}.tox .tox-dropzone p{color:rgba(255,255,255,.5);margin:0 0 16px 0}.tox .tox-edit-area{display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-edit-area::before{border:0 solid transparent;border-radius:4px;content:'';inset:0;opacity:0;pointer-events:none;position:absolute;transition:opacity .15s;z-index:1}@media (forced-colors:active){.tox .tox-edit-area::before{border:0 solid highlight}}.tox .tox-edit-area__iframe{background-color:#fff;border:0;box-sizing:border-box;flex:1;height:100%;position:absolute;width:100%}.tox.tox-edit-focus .tox-edit-area::before{opacity:1}.tox.tox-inline-edit-area{border:1px dotted #000}.tox .tox-editor-container{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-editor-header{display:grid;grid-template-columns:1fr min-content;z-index:2}.tox:not(.tox-tinymce-inline) .tox-editor-header{background-color:#222f3e;border-bottom:none;box-shadow:none;padding:4px 0}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(.tox-editor-dock-transition){transition:box-shadow .5s}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header{border-top:1px solid #000;box-shadow:none}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:#222f3e;box-shadow:0 4px 4px -3px rgba(0,0,0,.25);padding:4px 0}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header{box-shadow:0 4px 4px -3px rgba(0,0,0,.25)}.tox.tox:not(.tox-tinymce-inline) .tox-editor-header.tox-editor-header--empty{background:0 0;border:none;box-shadow:none;padding:0}.tox-editor-dock-fadeout{opacity:0;visibility:hidden}.tox-editor-dock-fadein{opacity:1;visibility:visible}.tox-editor-dock-transition{transition:visibility 0s linear .25s,opacity .25s ease}.tox-editor-dock-transition.tox-editor-dock-fadein{transition-delay:0s}.tox .tox-control-wrap{flex:1;position:relative}.tox .tox-control-wrap:not(.tox-control-wrap--status-invalid) .tox-control-wrap__status-icon-invalid,.tox .tox-control-wrap:not(.tox-control-wrap--status-unknown) .tox-control-wrap__status-icon-unknown,.tox .tox-control-wrap:not(.tox-control-wrap--status-valid) .tox-control-wrap__status-icon-valid{display:none}.tox .tox-control-wrap svg{display:block}.tox .tox-control-wrap__status-icon-wrap{position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-control-wrap__status-icon-invalid svg{fill:#c00}.tox .tox-control-wrap__status-icon-unknown svg{fill:orange}.tox .tox-control-wrap__status-icon-valid svg{fill:green}.tox:not([dir=rtl]) .tox-control-wrap--status-invalid .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-unknown .tox-textfield,.tox:not([dir=rtl]) .tox-control-wrap--status-valid .tox-textfield{padding-right:32px}.tox:not([dir=rtl]) .tox-control-wrap__status-icon-wrap{right:4px}.tox[dir=rtl] .tox-control-wrap--status-invalid .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-unknown .tox-textfield,.tox[dir=rtl] .tox-control-wrap--status-valid .tox-textfield{padding-left:32px}.tox[dir=rtl] .tox-control-wrap__status-icon-wrap{left:4px}.tox .tox-custom-preview{border-color:#000;border-radius:3px;border-style:solid;border-width:1px;flex:1;padding:8px}.tox .tox-autocompleter{max-width:25em}.tox .tox-autocompleter .tox-menu{box-sizing:border-box;max-width:25em}.tox .tox-autocompleter .tox-autocompleter-highlight{font-weight:700}.tox .tox-color-input{display:flex;position:relative;z-index:1}.tox .tox-color-input .tox-textfield{z-index:-1}.tox .tox-color-input span{border-color:rgba(42,55,70,.2);border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;height:24px;position:absolute;top:6px;width:24px}@media (forced-colors:active){.tox .tox-color-input span{border-color:currentColor;border-width:2px!important;forced-color-adjust:none}}.tox .tox-color-input span:focus:not([aria-disabled=true]),.tox .tox-color-input span:hover:not([aria-disabled=true]){border-color:#207ab7;cursor:pointer}.tox .tox-color-input span::before{background-image:linear-gradient(45deg,rgba(255,255,255,.25) 25%,transparent 25%),linear-gradient(-45deg,rgba(255,255,255,.25) 25%,transparent 25%),linear-gradient(45deg,transparent 75%,rgba(255,255,255,.25) 75%),linear-gradient(-45deg,transparent 75%,rgba(255,255,255,.25) 75%);background-position:0 0,0 6px,6px -6px,-6px 0;background-size:12px 12px;border:1px solid #2b3b4e;border-radius:3px;box-sizing:border-box;content:'';height:24px;left:-1px;position:absolute;top:-1px;width:24px;z-index:-1}@media (forced-colors:active){.tox .tox-color-input span::before{border:none}}.tox .tox-color-input span[aria-disabled=true]{cursor:not-allowed}.tox:not([dir=rtl]) .tox-color-input .tox-textfield{padding-left:36px}.tox:not([dir=rtl]) .tox-color-input span{left:6px}.tox[dir=rtl] .tox-color-input .tox-textfield{padding-right:36px}.tox[dir=rtl] .tox-color-input span{right:6px}.tox .tox-label,.tox .tox-toolbar-label{color:rgba(255,255,255,.5);display:block;font-size:14px;font-style:normal;font-weight:400;line-height:1.3;padding:0 8px 0 0;text-transform:none;white-space:nowrap}.tox .tox-toolbar-label{padding:0 8px}.tox[dir=rtl] .tox-label{padding:0 0 0 8px}.tox .tox-form{display:flex;flex:1;flex-direction:column}.tox .tox-form__group{box-sizing:border-box;margin-bottom:4px}.tox .tox-form-group--maximize{flex:1}.tox .tox-form__group--error{color:#c00}.tox .tox-form__group--collection{display:flex}.tox .tox-form__grid{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:space-between}.tox .tox-form__grid--2col>.tox-form__group{width:calc(50% - (8px / 2))}.tox .tox-form__grid--3col>.tox-form__group{width:calc(100% / 3 - (8px / 2))}.tox .tox-form__grid--4col>.tox-form__group{width:calc(25% - (8px / 2))}.tox .tox-form__controls-h-stack{align-items:center;display:flex}.tox .tox-form__group--inline{align-items:center;display:flex}.tox .tox-form__group--stretched{display:flex;flex:1;flex-direction:column}.tox .tox-form__group--stretched .tox-textarea{flex:1}.tox .tox-form__group--stretched .tox-navobj{display:flex;flex:1}.tox .tox-form__group--stretched .tox-navobj :nth-child(2){flex:1;height:100%}.tox:not([dir=rtl]) .tox-form__controls-h-stack>:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-form__controls-h-stack>:not(:first-child){margin-right:4px}.tox .tox-lock.tox-locked .tox-lock-icon__unlock,.tox .tox-lock:not(.tox-locked) .tox-lock-icon__lock{display:none}.tox .tox-listboxfield .tox-listbox--select,.tox .tox-textarea,.tox .tox-textarea-wrap .tox-textarea:focus,.tox .tox-textfield,.tox .tox-toolbar-textfield{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#2b3b4e;border-color:#000;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-textarea[disabled],.tox .tox-textfield[disabled]{background-color:#222f3e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-custom-editor:focus-within,.tox .tox-listboxfield .tox-listbox--select:focus,.tox .tox-textarea-wrap:focus-within,.tox .tox-textarea:focus,.tox .tox-textfield:focus{background-color:#2b3b4e;border-color:#207ab7;box-shadow:none;outline:2px solid rgba(32,122,183,.25)}.tox .tox-toolbar-textfield{border-width:0;margin-bottom:3px;margin-top:2px;max-width:250px}.tox .tox-naked-btn{background-color:transparent;border:0;border-color:transparent;box-shadow:unset;color:#207ab7;cursor:pointer;display:block;margin:0;padding:0}.tox .tox-naked-btn svg{display:block;fill:#fff}.tox:not([dir=rtl]) .tox-toolbar-textfield+*{margin-left:4px}.tox[dir=rtl] .tox-toolbar-textfield+*{margin-right:4px}.tox .tox-listboxfield{cursor:pointer;position:relative}.tox .tox-listboxfield .tox-listbox--select[disabled]{background-color:#19232e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-listbox__select-label{cursor:default;flex:1;margin:0 4px}.tox .tox-listbox__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-listbox__select-chevron svg{fill:#fff}@media (forced-colors:active){.tox .tox-listbox__select-chevron svg{fill:currentColor!important}}.tox .tox-listboxfield .tox-listbox--select{align-items:center;display:flex}.tox:not([dir=rtl]) .tox-listboxfield svg{right:8px}.tox[dir=rtl] .tox-listboxfield svg{left:8px}.tox .tox-selectfield{cursor:pointer;position:relative}.tox .tox-selectfield select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#2b3b4e;border-color:#000;border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;color:#fff;font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell,\"Helvetica Neue\",sans-serif;font-size:16px;line-height:24px;margin:0;min-height:34px;outline:0;padding:5px 4.75px;resize:none;width:100%}.tox .tox-selectfield select[disabled]{background-color:#19232e;color:rgba(255,255,255,.85);cursor:not-allowed}.tox .tox-selectfield select::-ms-expand{display:none}.tox .tox-selectfield select:focus{background-color:#2b3b4e;border-color:#207ab7;box-shadow:none;outline:2px solid rgba(32,122,183,.25)}.tox .tox-selectfield svg{pointer-events:none;position:absolute;top:50%;transform:translateY(-50%)}.tox:not([dir=rtl]) .tox-selectfield select[size=\"0\"],.tox:not([dir=rtl]) .tox-selectfield select[size=\"1\"]{padding-right:24px}.tox:not([dir=rtl]) .tox-selectfield svg{right:8px}.tox[dir=rtl] .tox-selectfield select[size=\"0\"],.tox[dir=rtl] .tox-selectfield select[size=\"1\"]{padding-left:24px}.tox[dir=rtl] .tox-selectfield svg{left:8px}.tox .tox-textarea-wrap{border-color:#000;border-radius:3px;border-style:solid;border-width:1px;display:flex;flex:1;overflow:hidden}.tox .tox-textarea{-webkit-appearance:textarea;-moz-appearance:textarea;appearance:textarea;white-space:pre-wrap}.tox .tox-textarea-wrap .tox-textarea{border:none}.tox .tox-textarea-wrap .tox-textarea:focus{border:none}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}.tox .tox-help__more-link{list-style:none;margin-top:1em}.tox .tox-imagepreview{background-color:#666;height:380px;overflow:hidden;position:relative;width:100%}.tox .tox-imagepreview.tox-imagepreview__loaded{overflow:auto}.tox .tox-imagepreview__container{display:flex;left:100vw;position:absolute;top:100vw}.tox .tox-imagepreview__image{background:url(data:image/gif;base64,R0lGODdhDAAMAIABAMzMzP///ywAAAAADAAMAAACFoQfqYeabNyDMkBQb81Uat85nxguUAEAOw==)}.tox .tox-image-tools .tox-spacer{flex:1}.tox .tox-image-tools .tox-bar{align-items:center;display:flex;height:60px;justify-content:center}.tox .tox-image-tools .tox-imagepreview,.tox .tox-image-tools .tox-imagepreview+.tox-bar{margin-top:8px}.tox .tox-image-tools .tox-croprect-block{background:#000;opacity:.5;position:absolute;zoom:1}.tox .tox-image-tools .tox-croprect-handle{border:2px solid #fff;height:20px;left:0;position:absolute;top:0;width:20px}.tox .tox-image-tools .tox-croprect-handle-move{border:0;cursor:move;position:absolute}.tox .tox-image-tools .tox-croprect-handle-nw{border-width:2px 0 0 2px;cursor:nw-resize;left:100px;margin:-2px 0 0 -2px;top:100px}.tox .tox-image-tools .tox-croprect-handle-ne{border-width:2px 2px 0 0;cursor:ne-resize;left:200px;margin:-2px 0 0 -20px;top:100px}.tox .tox-image-tools .tox-croprect-handle-sw{border-width:0 0 2px 2px;cursor:sw-resize;left:100px;margin:-20px 2px 0 -2px;top:200px}.tox .tox-image-tools .tox-croprect-handle-se{border-width:0 2px 2px 0;cursor:se-resize;left:200px;margin:-20px 0 0 -20px;top:200px}.tox .tox-insert-table-picker{background-color:#222f3e;display:flex;flex-wrap:wrap;width:170px}.tox .tox-insert-table-picker>div{border-color:#000;border-style:solid;border-width:0 1px 1px 0;box-sizing:border-box;height:17px;width:17px}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:0 -4px}.tox .tox-insert-table-picker .tox-insert-table-picker__selected{background-color:rgba(32,122,183,.5);border-color:rgba(32,122,183,.5)}@media (forced-colors:active){.tox .tox-insert-table-picker .tox-insert-table-picker__selected{border-color:Highlight;filter:contrast(50%)}}.tox .tox-insert-table-picker__label{color:#fff;display:block;font-size:14px;padding:4px;text-align:center;width:100%}.tox:not([dir=rtl]) .tox-insert-table-picker>div:nth-child(10n){border-right:0}.tox[dir=rtl] .tox-insert-table-picker>div:nth-child(10n+1){border-right:0}.tox .tox-menu{background-color:#2b3b4e;border:1px solid #000;border-radius:3px;box-shadow:0 4px 8px 0 rgba(42,55,70,.1);display:inline-block;overflow:hidden;vertical-align:top;z-index:1150}.tox .tox-menu.tox-collection.tox-collection--list{padding:0 0}.tox .tox-menu.tox-collection.tox-collection--toolbar{padding:4px}.tox .tox-menu.tox-collection.tox-collection--grid{padding:4px}@media only screen and (min-width:768px){.tox .tox-menu .tox-collection__item-label{overflow-wrap:break-word;word-break:normal}.tox .tox-dialog__popups .tox-menu .tox-collection__item-label{word-break:break-all}}.tox .tox-menu__label blockquote,.tox .tox-menu__label code,.tox .tox-menu__label h1,.tox .tox-menu__label h2,.tox .tox-menu__label h3,.tox .tox-menu__label h4,.tox .tox-menu__label h5,.tox .tox-menu__label h6,.tox .tox-menu__label p{margin:0}.tox .tox-menubar{background:url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E\") left 0 top 0 #222f3e;background-color:#222f3e;display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;grid-column:1/-1;grid-row:1;padding:0 4px 0 4px}.tox .tox-promotion+.tox-menubar{grid-column:1}.tox .tox-promotion{background:url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E\") left 0 top 0 #222f3e;background-color:#222f3e;grid-column:2;grid-row:1;padding-inline-end:8px;padding-inline-start:4px;padding-top:5px}.tox .tox-promotion-link{align-items:unsafe center;background-color:#e8f1f8;border-radius:5px;color:#086be6;cursor:pointer;display:flex;font-size:14px;height:26.6px;padding:4px 8px;white-space:nowrap}.tox .tox-promotion-link:hover{background-color:#b4d7ff}.tox .tox-promotion-link:focus{background-color:#d9edf7}.tox .tox-mbtn{align-items:center;background:#222f3e;border:0;border-radius:3px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:2px 0 3px 0;outline:0;padding:0 4px;text-transform:none;width:auto}.tox .tox-mbtn[disabled]{background-color:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-mbtn:focus:not(:disabled){background:#4a5562;border:0;box-shadow:none;color:#fff;position:relative;z-index:1}.tox .tox-mbtn:focus:not(:disabled)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-mbtn:focus:not(:disabled)::after{border:2px solid highlight}}.tox .tox-mbtn--active,.tox .tox-mbtn:not(:disabled).tox-mbtn--active:focus{background:#757d87;border:0;box-shadow:none;color:#fff}.tox .tox-mbtn:hover:not(:disabled):not(.tox-mbtn--active){background:#4a5562;border:0;box-shadow:none;color:#fff}.tox .tox-mbtn__select-label{cursor:default;font-weight:400;margin:0 4px}.tox .tox-mbtn[disabled] .tox-mbtn__select-label{cursor:not-allowed}.tox .tox-mbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px;display:none}.tox .tox-notification{border-radius:3px;border-style:solid;border-width:1px;box-shadow:none;box-sizing:border-box;display:grid;font-size:14px;font-weight:400;grid-template-columns:minmax(40px,1fr) auto minmax(40px,1fr);margin-left:auto;margin-right:auto;margin-top:4px;opacity:0;padding:4px;transition:transform .1s ease-in,opacity 150ms ease-in;width:-moz-max-content;width:max-content}.tox .tox-notification a{cursor:pointer;text-decoration:underline}.tox .tox-notification p{font-size:14px;font-weight:400}.tox .tox-notification:focus{border-color:#207ab7;box-shadow:none}.tox .tox-notification--in{opacity:1}.tox .tox-notification--success{background-color:#334840;border-color:#3c5440;color:#fff}.tox .tox-notification--success p{color:#fff}.tox .tox-notification--success a{color:#b5d199}.tox .tox-notification--success a:focus,.tox .tox-notification--success a:hover{color:#82b153;text-decoration:underline}.tox .tox-notification--success a:focus-visible{border-radius:1px;outline:2px solid #b5d199;outline-offset:2px}.tox .tox-notification--success a:active{color:#689041;text-decoration:underline}.tox .tox-notification--success svg{fill:#fff}.tox .tox-notification--error{background-color:#442632;border-color:#55212b;color:#fff}.tox .tox-notification--error p{color:#fff}.tox .tox-notification--error a{color:#e68080}.tox .tox-notification--error a:focus,.tox .tox-notification--error a:hover{color:#d42b2b;text-decoration:underline}.tox .tox-notification--error a:focus-visible{border-radius:1px;outline:2px solid #e68080;outline-offset:2px}.tox .tox-notification--error a:active{color:#a22;text-decoration:underline}.tox .tox-notification--error svg{fill:#fff}.tox .tox-notification--warn,.tox .tox-notification--warning{background-color:#222f3e;border-color:#000;color:#fff0b3}.tox .tox-notification--warn p,.tox .tox-notification--warning p{color:#fff0b3}.tox .tox-notification--warn a,.tox .tox-notification--warning a{color:#fc0}.tox .tox-notification--warn a:focus,.tox .tox-notification--warn a:hover,.tox .tox-notification--warning a:focus,.tox .tox-notification--warning a:hover{color:#997a00;text-decoration:underline}.tox .tox-notification--warn a:focus-visible,.tox .tox-notification--warning a:focus-visible{border-radius:1px;outline:2px solid #fc0;outline-offset:2px}.tox .tox-notification--warn a:active,.tox .tox-notification--warning a:active{color:#665200;text-decoration:underline}.tox .tox-notification--warn svg,.tox .tox-notification--warning svg{fill:#fff0b3}.tox .tox-notification--info{background-color:#254161;border-color:#264972;color:#fff}.tox .tox-notification--info p{color:#fff}.tox .tox-notification--info a{color:#83b7f3}.tox .tox-notification--info a:focus,.tox .tox-notification--info a:hover{color:#2681ea;text-decoration:underline}.tox .tox-notification--info a:focus-visible{border-radius:1px;outline:2px solid #83b7f3;outline-offset:2px}.tox .tox-notification--info a:active{color:#1368c9;text-decoration:underline}.tox .tox-notification--info svg{fill:#fff}.tox .tox-notification__body{align-self:center;color:#fff;font-size:14px;grid-column-end:3;grid-column-start:2;grid-row-end:2;grid-row-start:1;text-align:center;white-space:normal;word-break:break-all;word-break:break-word}.tox .tox-notification__body>*{margin:0}.tox .tox-notification__body>*+*{margin-top:1rem}.tox .tox-notification__icon{align-self:center;grid-column-end:2;grid-column-start:1;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification__icon svg{display:block}.tox .tox-notification__dismiss{align-self:start;grid-column-end:4;grid-column-start:3;grid-row-end:2;grid-row-start:1;justify-self:end}.tox .tox-notification .tox-progress-bar{grid-column-end:4;grid-column-start:1;grid-row-end:3;grid-row-start:2;justify-self:center}.tox .tox-notification-container-dock-fadeout{opacity:0;visibility:hidden}.tox .tox-notification-container-dock-fadein{opacity:1;visibility:visible}.tox .tox-notification-container-dock-transition{transition:visibility 0s linear .3s,opacity .3s ease}.tox .tox-notification-container-dock-transition.tox-notification-container-dock-fadein{transition-delay:0s}.tox .tox-pop{display:inline-block;position:relative}.tox .tox-pop--resizing{transition:width .1s ease}.tox .tox-pop--resizing .tox-toolbar,.tox .tox-pop--resizing .tox-toolbar__group{flex-wrap:nowrap}.tox .tox-pop--transition{transition:.15s ease;transition-property:left,right,top,bottom}.tox .tox-pop--transition::after,.tox .tox-pop--transition::before{transition:all .15s,visibility 0s,opacity 75ms ease 75ms}.tox .tox-pop__dialog{background-color:#222f3e;border:1px solid #000;border-radius:3px;box-shadow:0 0 2px 0 rgba(42,55,70,.2),0 4px 8px 0 rgba(42,55,70,.15);min-width:0;overflow:hidden}.tox .tox-pop__dialog>:not(.tox-toolbar){margin:4px 4px 4px 8px}.tox .tox-pop__dialog .tox-toolbar{background-color:transparent;margin-bottom:-1px}.tox .tox-pop::after,.tox .tox-pop::before{border-style:solid;content:'';display:block;height:0;opacity:1;position:absolute;width:0}@media (forced-colors:active){.tox .tox-pop::after,.tox .tox-pop::before{content:none}}.tox .tox-pop.tox-pop--inset::after,.tox .tox-pop.tox-pop--inset::before{opacity:0;transition:all 0s .15s,visibility 0s,opacity 75ms ease}.tox .tox-pop.tox-pop--bottom::after,.tox .tox-pop.tox-pop--bottom::before{left:50%;top:100%}.tox .tox-pop.tox-pop--bottom::after{border-color:#222f3e transparent transparent transparent;border-width:8px;margin-left:-8px;margin-top:-1px}.tox .tox-pop.tox-pop--bottom::before{border-color:#000 transparent transparent transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--top::after,.tox .tox-pop.tox-pop--top::before{left:50%;top:0;transform:translateY(-100%)}.tox .tox-pop.tox-pop--top::after{border-color:transparent transparent #222f3e transparent;border-width:8px;margin-left:-8px;margin-top:1px}.tox .tox-pop.tox-pop--top::before{border-color:transparent transparent #000 transparent;border-width:9px;margin-left:-9px}.tox .tox-pop.tox-pop--left::after,.tox .tox-pop.tox-pop--left::before{left:0;top:calc(50% - 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--left::after{border-color:transparent #222f3e transparent transparent;border-width:8px;margin-left:-15px}.tox .tox-pop.tox-pop--left::before{border-color:transparent #000 transparent transparent;border-width:10px;margin-left:-19px}.tox .tox-pop.tox-pop--right::after,.tox .tox-pop.tox-pop--right::before{left:100%;top:calc(50% + 1px);transform:translateY(-50%)}.tox .tox-pop.tox-pop--right::after{border-color:transparent transparent transparent #222f3e;border-width:8px;margin-left:-1px}.tox .tox-pop.tox-pop--right::before{border-color:transparent transparent transparent #000;border-width:10px;margin-left:-1px}.tox .tox-pop.tox-pop--align-left::after,.tox .tox-pop.tox-pop--align-left::before{left:20px}.tox .tox-pop.tox-pop--align-right::after,.tox .tox-pop.tox-pop--align-right::before{left:calc(100% - 20px)}.tox .tox-sidebar-wrap{display:flex;flex-direction:row;flex-grow:1;min-height:0}.tox .tox-sidebar{background-color:#222f3e;display:flex;flex-direction:row;justify-content:flex-end}.tox .tox-sidebar__slider{display:flex;overflow:hidden}.tox .tox-sidebar__pane-container{display:flex}.tox .tox-sidebar__pane{display:flex}.tox .tox-sidebar--sliding-closed{opacity:0}.tox .tox-sidebar--sliding-open{opacity:1}.tox .tox-sidebar--sliding-growing,.tox .tox-sidebar--sliding-shrinking{transition:width .5s ease,opacity .5s ease}.tox .tox-selector{background-color:#4099ff;border-color:#4099ff;border-style:solid;border-width:1px;box-sizing:border-box;display:inline-block;height:10px;position:absolute;width:10px}.tox.tox-platform-touch .tox-selector{height:12px;width:12px}.tox .tox-slider{align-items:center;display:flex;flex:1;height:24px;justify-content:center;position:relative}.tox .tox-slider__rail{background-color:transparent;border:1px solid #000;border-radius:3px;height:10px;min-width:120px;width:100%}.tox .tox-slider__handle{background-color:#207ab7;border:2px solid #185d8c;border-radius:3px;box-shadow:none;height:24px;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%);width:14px}.tox .tox-form__controls-h-stack>.tox-slider:not(:first-of-type){margin-inline-start:8px}.tox .tox-form__controls-h-stack>.tox-form__group+.tox-slider{margin-inline-start:32px}.tox .tox-form__controls-h-stack>.tox-slider+.tox-form__group{margin-inline-start:32px}.tox .tox-source-code{overflow:auto}.tox .tox-spinner{display:flex}.tox .tox-spinner>div{animation:tam-bouncing-dots 1.5s ease-in-out 0s infinite both;background-color:rgba(255,255,255,.5);border-radius:100%;height:8px;width:8px}.tox .tox-spinner>div:nth-child(1){animation-delay:-.32s}.tox .tox-spinner>div:nth-child(2){animation-delay:-.16s}@keyframes tam-bouncing-dots{0%,100%,80%{transform:scale(0)}40%{transform:scale(1)}}.tox:not([dir=rtl]) .tox-spinner>div:not(:first-child){margin-left:4px}.tox[dir=rtl] .tox-spinner>div:not(:first-child){margin-right:4px}.tox .tox-statusbar{align-items:center;background-color:#222f3e;border-top:1px solid #000;color:#fff;display:flex;flex:0 0 auto;font-size:12px;font-weight:400;height:18px;overflow:hidden;padding:0 8px;position:relative;text-transform:uppercase}.tox .tox-statusbar__path{display:flex;flex:1 1 auto;text-overflow:ellipsis;white-space:nowrap}.tox .tox-statusbar__right-container{display:flex;justify-content:flex-end;white-space:nowrap}.tox .tox-statusbar__help-text{text-align:center}.tox .tox-statusbar__text-container{align-items:flex-start;display:flex;flex:1 1 auto;height:16px;justify-content:space-between;overflow:hidden}@media only screen and (min-width:768px){.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__help-text,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__path,.tox .tox-statusbar__text-container.tox-statusbar__text-container-3-cols>.tox-statusbar__right-container{flex:0 0 calc(100% / 3)}}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-end{justify-content:flex-end}.tox .tox-statusbar__text-container.tox-statusbar__text-container--flex-start{justify-content:flex-start}.tox .tox-statusbar__text-container.tox-statusbar__text-container--space-around{justify-content:space-around}.tox .tox-statusbar__path>*{display:inline;white-space:nowrap}.tox .tox-statusbar__wordcount{flex:0 0 auto;margin-left:1ch}@media only screen and (max-width:767px){.tox .tox-statusbar__text-container .tox-statusbar__help-text{display:none}.tox .tox-statusbar__text-container .tox-statusbar__help-text:only-child{display:block}}.tox .tox-statusbar a,.tox .tox-statusbar__path-item,.tox .tox-statusbar__wordcount{color:#fff;position:relative;text-decoration:none}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){color:#fff;cursor:pointer}.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar a:focus-visible::after,.tox .tox-statusbar__path-item:focus-visible::after,.tox .tox-statusbar__wordcount:focus-visible::after{border:2px solid highlight}}.tox .tox-statusbar__branding svg{fill:rgba(255,255,255,.8);height:1em;margin-left:.3em;width:auto}@media (forced-colors:active){.tox .tox-statusbar__branding svg{fill:currentColor}}.tox .tox-statusbar__branding a{align-items:center;display:inline-flex}.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg,.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg{fill:#fff}.tox .tox-statusbar__resize-handle{align-items:flex-end;align-self:stretch;cursor:nwse-resize;display:flex;flex:0 0 auto;justify-content:flex-end;margin-bottom:3px;margin-left:4px;margin-right:calc(3px - 8px);margin-top:3px;padding-bottom:0;padding-left:0;padding-right:0;position:relative}.tox .tox-statusbar__resize-handle svg{display:block;fill:rgba(255,255,255,.5)}.tox .tox-statusbar__resize-handle:focus svg,.tox .tox-statusbar__resize-handle:hover svg{fill:#fff}.tox .tox-statusbar__resize-handle:focus-visible{background-color:transparent;border-radius:1px 1px -4px 1px;box-shadow:0 0 0 2px transparent}.tox .tox-statusbar__resize-handle:focus-visible::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-statusbar__resize-handle:focus-visible::after{border:2px solid highlight}}.tox:not([dir=rtl]) .tox-statusbar__path>*{margin-right:4px}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:2ch}.tox[dir=rtl] .tox-statusbar{flex-direction:row-reverse}.tox[dir=rtl] .tox-statusbar__path>*{margin-left:4px}.tox[dir=rtl] .tox-statusbar__branding svg{margin-left:0;margin-right:.3em}.tox .tox-throbber{z-index:1299}.tox .tox-throbber__busy-spinner{align-items:center;background-color:rgba(34,47,62,.6);bottom:0;display:flex;justify-content:center;left:0;position:absolute;right:0;top:0}.tox .tox-tbtn{align-items:center;background:#222f3e;border:0;border-radius:3px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:34px;justify-content:center;margin:3px 0 2px 0;outline:0;padding:0;text-transform:none;width:34px}@media (forced-colors:active){.tox .tox-tbtn.tox-tbtn:hover,.tox .tox-tbtn:hover{outline:1px dashed currentColor}.tox .tox-tbtn.tox-tbtn--active,.tox .tox-tbtn.tox-tbtn--enabled,.tox .tox-tbtn.tox-tbtn--enabled:focus,.tox .tox-tbtn.tox-tbtn--enabled:hover,.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){outline:1px solid currentColor;position:relative}}.tox .tox-tbtn svg{display:block;fill:#fff}@media (forced-colors:active){.tox .tox-tbtn svg{fill:currentColor!important}.tox .tox-tbtn svg.tox-tbtn--enabled,.tox .tox-tbtn svg:focus:not(.tox-tbtn--disabled){fill:currentColor!important}.tox .tox-tbtn svg .tox-tbtn:disabled,.tox .tox-tbtn svg .tox-tbtn:disabled:hover,.tox .tox-tbtn svg.tox-tbtn--disabled,.tox .tox-tbtn svg.tox-tbtn--disabled:hover{filter:contrast(0)}}.tox .tox-tbtn.tox-tbtn-more{padding-left:5px;padding-right:5px;width:inherit}.tox .tox-tbtn:focus{background:#4a5562;border:0;box-shadow:none;position:relative;z-index:1}.tox .tox-tbtn:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn:focus::after{border:2px solid highlight}}.tox .tox-tbtn:hover{background:#4a5562;border:0;box-shadow:none;color:#fff}.tox .tox-tbtn:hover svg{fill:#fff}.tox .tox-tbtn:active{background:#757d87;border:0;box-shadow:none;color:#fff}.tox .tox-tbtn:active svg{fill:#fff}.tox .tox-tbtn--disabled .tox-tbtn--enabled svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--disabled,.tox .tox-tbtn--disabled:hover,.tox .tox-tbtn:disabled,.tox .tox-tbtn:disabled:hover{background:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-tbtn--disabled svg,.tox .tox-tbtn--disabled:hover svg,.tox .tox-tbtn:disabled svg,.tox .tox-tbtn:disabled:hover svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--active,.tox .tox-tbtn--enabled,.tox .tox-tbtn--enabled:focus,.tox .tox-tbtn--enabled:hover{background:#757d87;border:0;box-shadow:none;color:#fff;position:relative}.tox .tox-tbtn--active>*,.tox .tox-tbtn--enabled:focus>*,.tox .tox-tbtn--enabled:hover>*,.tox .tox-tbtn--enabled>*{transform:none}.tox .tox-tbtn--active svg,.tox .tox-tbtn--enabled svg,.tox .tox-tbtn--enabled:focus svg,.tox .tox-tbtn--enabled:hover svg{fill:#fff}.tox .tox-tbtn--active.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:focus.tox-tbtn--disabled svg,.tox .tox-tbtn--enabled:hover.tox-tbtn--disabled svg{fill:rgba(255,255,255,.5)}.tox .tox-tbtn--enabled:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-tbtn--enabled:focus::after{border:2px solid highlight}}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled){color:#fff}.tox .tox-tbtn:focus:not(.tox-tbtn--disabled) svg{fill:#fff}.tox .tox-tbtn:active>*{transform:none}.tox .tox-tbtn--md{height:51px;width:51px}.tox .tox-tbtn--lg{flex-direction:column;height:68px;width:68px}.tox .tox-tbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tbtn--labeled{padding:0 4px;width:unset}.tox .tox-tbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-number-input{background:0 0;border-radius:3px;display:flex;margin:3px 0 2px 0;position:relative;width:auto}.tox .tox-number-input:focus{background:#4a5562}.tox .tox-number-input:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper{display:flex;pointer-events:none;position:relative;text-align:center}.tox .tox-number-input .tox-input-wrapper:focus{background-color:#4a5562;z-index:1}.tox .tox-number-input .tox-input-wrapper:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:focus::after{border:2px solid highlight}}.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input .tox-input-wrapper:has(input:focus)::after{border:2px solid highlight}}.tox .tox-number-input input{border-radius:3px;color:#fff;font-size:14px;margin:2px 0;pointer-events:all;position:relative;width:60px}.tox .tox-number-input input:hover{background:#4a5562;color:#fff}.tox .tox-number-input input:focus{background-color:#4a5562}.tox .tox-number-input input:disabled{background:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-number-input button{color:#fff;height:34px;position:relative;text-align:center;width:24px}@media (forced-colors:active){.tox .tox-number-input button:active,.tox .tox-number-input button:focus,.tox .tox-number-input button:hover{outline:1px solid currentColor!important}}.tox .tox-number-input button svg{display:block;fill:#fff;margin:0 auto;transform:scale(.67)}@media (forced-colors:active){.tox .tox-number-input button svg,.tox .tox-number-input button svg:active,.tox .tox-number-input button svg:hover{fill:currentColor!important}.tox .tox-number-input button svg:disabled{filter:contrast(0)}}.tox .tox-number-input button:focus{background:#4a5562;z-index:1}.tox .tox-number-input button:focus::after{border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-number-input button:focus::after{border:2px solid highlight}}.tox .tox-number-input button:hover{background:#4a5562;border:0;box-shadow:none;color:#fff}.tox .tox-number-input button:hover svg{fill:#fff}.tox .tox-number-input button:active{background:#757d87;border:0;box-shadow:none;color:#fff}.tox .tox-number-input button:active svg{fill:#fff}.tox .tox-number-input button:disabled{background:#222f3e;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-number-input button:disabled svg{fill:rgba(255,255,255,.5)}.tox .tox-number-input button.minus{border-radius:3px 0 0 3px}.tox .tox-number-input button.plus{border-radius:0 3px 3px 0}.tox .tox-number-input:focus:not(:active)>.tox-input-wrapper,.tox .tox-number-input:focus:not(:active)>button{background:#4a5562}.tox .tox-tbtn--select{margin:3px 0 2px 0;padding:0 4px;width:auto}.tox .tox-tbtn__select-label{cursor:default;font-weight:400;height:initial;margin:0 4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tbtn__select-chevron{align-items:center;display:flex;justify-content:center;width:16px}.tox .tox-tbtn__select-chevron svg{fill:rgba(255,255,255,.5)}@media (forced-colors:active){.tox .tox-tbtn__select-chevron svg{fill:currentColor}}.tox .tox-tbtn--bespoke{background:0 0}.tox .tox-tbtn--bespoke:focus{background:#4a5562}.tox .tox-tbtn--bespoke+.tox-tbtn--bespoke{margin-inline-start:0}.tox .tox-tbtn--bespoke .tox-tbtn__select-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:7em}.tox .tox-tbtn--disabled .tox-tbtn__select-label,.tox .tox-tbtn--select:disabled .tox-tbtn__select-label{cursor:not-allowed}.tox .tox-split-button{border:0;border-radius:3px;box-sizing:border-box;display:flex;margin:3px 0 2px 0}.tox .tox-split-button:hover{box-shadow:0 0 0 1px #4a5562 inset}.tox .tox-split-button:focus{background:#4a5562;box-shadow:none;color:#fff;position:relative;z-index:1}.tox .tox-split-button:focus::after{pointer-events:none;border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-split-button:focus::after{border:2px solid highlight}}.tox .tox-split-button>*{border-radius:0}.tox .tox-split-button>:nth-child(1){border-bottom-left-radius:3px;border-top-left-radius:3px}.tox .tox-split-button>:nth-child(2){border-bottom-right-radius:3px;border-top-right-radius:3px}.tox .tox-split-button__chevron{width:16px}.tox .tox-split-button__chevron svg{fill:rgba(255,255,255,.5)}@media (forced-colors:active){.tox .tox-split-button__chevron svg{fill:currentColor}}.tox .tox-split-button .tox-tbtn{margin:0}.tox .tox-split-button:focus .tox-tbtn{background-color:transparent}.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:focus,.tox .tox-split-button.tox-tbtn--disabled .tox-tbtn:hover,.tox .tox-split-button.tox-tbtn--disabled:focus,.tox .tox-split-button.tox-tbtn--disabled:hover{background:#222f3e;box-shadow:none;color:rgba(255,255,255,.5)}.tox.tox-platform-touch .tox-split-button .tox-tbtn--select{padding:0 0}.tox.tox-platform-touch .tox-split-button .tox-tbtn:not(.tox-tbtn--select):first-child{width:30px}.tox.tox-platform-touch .tox-split-button__chevron{width:20px}.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-highlight-bg-color__color,.tox .tox-split-button.tox-tbtn--disabled svg #tox-icon-text-color__color{opacity:.6}.tox .tox-toolbar-overlord{background-color:#222f3e}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background-attachment:local;background-color:#222f3e;background-image:repeating-linear-gradient(#000 0 1px,transparent 1px 39px);background-position:center top 39px;background-repeat:no-repeat;background-size:calc(100% - 4px * 2) calc(100% - 39px);display:flex;flex:0 0 auto;flex-shrink:0;flex-wrap:wrap;padding:0 0;transform:perspective(1px)}.tox .tox-toolbar-overlord>.tox-toolbar,.tox .tox-toolbar-overlord>.tox-toolbar__overflow,.tox .tox-toolbar-overlord>.tox-toolbar__primary{background-position:center top 0;background-size:calc(100% - 4px * 2) calc(100% - 0px)}.tox .tox-toolbar__overflow.tox-toolbar__overflow--closed{height:0;opacity:0;padding-bottom:0;padding-top:0;visibility:hidden}.tox .tox-toolbar__overflow--growing{transition:height .3s ease,opacity .2s linear .1s}.tox .tox-toolbar__overflow--shrinking{transition:opacity .3s ease,height .2s linear .1s,visibility 0s linear .3s}.tox .tox-anchorbar,.tox .tox-toolbar-overlord{grid-column:1/-1}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{border-top:1px solid #000;margin-top:-1px;padding-bottom:0;padding-top:0}@media (forced-colors:active){.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord{outline:1px solid currentColor}}.tox .tox-toolbar--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-pop .tox-toolbar{border-width:0}.tox .tox-toolbar--no-divider{background-image:none}.tox .tox-toolbar-overlord .tox-toolbar:not(.tox-toolbar--scrolling):first-child,.tox .tox-toolbar-overlord .tox-toolbar__primary{background-position:center top 39px}.tox .tox-editor-header>.tox-toolbar--scrolling,.tox .tox-toolbar-overlord .tox-toolbar--scrolling:first-child{background-image:none}.tox.tox-tinymce-aux .tox-toolbar__overflow{background-color:#222f3e;background-position:center top 43px;background-size:calc(100% - 8px * 2) calc(100% - 51px);border:none;border-radius:3px;box-shadow:0 0 2px 0 rgba(42,55,70,.2),0 4px 8px 0 rgba(42,55,70,.15);overscroll-behavior:none;padding:4px 0}@media (forced-colors:active){.tox.tox-tinymce-aux .tox-toolbar__overflow{border:solid}}.tox-pop .tox-pop__dialog .tox-toolbar{background-position:center top 43px;background-size:calc(100% - 4px * 2) calc(100% - 51px);padding:4px 0}.tox .tox-toolbar__group{align-items:center;display:flex;flex-wrap:wrap;margin:0 0;padding:0 4px 0 4px}.tox .tox-toolbar__group--pull-right{margin-left:auto}.tox .tox-toolbar--scrolling .tox-toolbar__group{flex-shrink:0;flex-wrap:nowrap}.tox:not([dir=rtl]) .tox-toolbar__group:not(:last-of-type){border-right:1px solid #000}.tox[dir=rtl] .tox-toolbar__group:not(:last-of-type){border-left:1px solid #000}.tox .tox-tooltip{display:inline-block;max-width:15em;padding:8px;pointer-events:none;position:relative;width:-moz-max-content;width:max-content;z-index:1150}.tox .tox-tooltip__body{background-color:#2a3746;border-radius:3px;box-shadow:none;color:#fff;font-size:12px;font-style:normal;font-weight:600;overflow-wrap:break-word;padding:4px 6px;text-transform:none}@media (forced-colors:active){.tox .tox-tooltip__body{outline:outset 1px}}.tox .tox-tooltip__arrow{position:absolute}.tox .tox-tooltip--down .tox-tooltip__arrow{border-left:8px solid transparent;border-right:8px solid transparent;border-top:8px solid #2a3746;bottom:0;left:50%;position:absolute;transform:translateX(-50%)}.tox .tox-tooltip--up .tox-tooltip__arrow{border-bottom:8px solid #2a3746;border-left:8px solid transparent;border-right:8px solid transparent;left:50%;position:absolute;top:0;transform:translateX(-50%)}.tox .tox-tooltip--right .tox-tooltip__arrow{border-bottom:8px solid transparent;border-left:8px solid #2a3746;border-top:8px solid transparent;position:absolute;right:0;top:50%;transform:translateY(-50%)}.tox .tox-tooltip--left .tox-tooltip__arrow{border-bottom:8px solid transparent;border-right:8px solid #2a3746;border-top:8px solid transparent;left:0;position:absolute;top:50%;transform:translateY(-50%)}.tox .tox-tree{display:flex;flex-direction:column}.tox .tox-tree .tox-trbtn{align-items:center;background:0 0;border:0;border-radius:4px;box-shadow:none;color:#fff;display:flex;flex:0 0 auto;font-size:14px;font-style:normal;font-weight:400;height:28px;margin-bottom:4px;margin-top:4px;outline:0;overflow:hidden;padding:0;padding-left:8px;text-transform:none}.tox .tox-tree .tox-trbtn .tox-tree__label{cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tox .tox-tree .tox-trbtn svg{display:block;fill:#fff}.tox .tox-tree .tox-trbtn:focus{background:#4a5562;border:0;box-shadow:none}.tox .tox-tree .tox-trbtn:hover{background:#4a5562;border:0;box-shadow:none;color:#fff}.tox .tox-tree .tox-trbtn:hover svg{fill:#fff}.tox .tox-tree .tox-trbtn:active{background:#6ea9d0;border:0;box-shadow:none;color:#fff}.tox .tox-tree .tox-trbtn:active svg{fill:#fff}.tox .tox-tree .tox-trbtn--disabled,.tox .tox-tree .tox-trbtn--disabled:hover,.tox .tox-tree .tox-trbtn:disabled,.tox .tox-tree .tox-trbtn:disabled:hover{background:0 0;border:0;box-shadow:none;color:rgba(255,255,255,.5);cursor:not-allowed}.tox .tox-tree .tox-trbtn--disabled svg,.tox .tox-tree .tox-trbtn--disabled:hover svg,.tox .tox-tree .tox-trbtn:disabled svg,.tox .tox-tree .tox-trbtn:disabled:hover svg{fill:rgba(255,255,255,.5)}.tox .tox-tree .tox-trbtn--enabled,.tox .tox-tree .tox-trbtn--enabled:hover{background:#6ea9d0;border:0;box-shadow:none;color:#fff}.tox .tox-tree .tox-trbtn--enabled:hover>*,.tox .tox-tree .tox-trbtn--enabled>*{transform:none}.tox .tox-tree .tox-trbtn--enabled svg,.tox .tox-tree .tox-trbtn--enabled:hover svg{fill:#fff}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled){color:#fff}.tox .tox-tree .tox-trbtn:focus:not(.tox-trbtn--disabled) svg{fill:#fff}.tox .tox-tree .tox-trbtn:active>*{transform:none}.tox .tox-tree .tox-trbtn--return{align-self:stretch;height:unset;width:16px}.tox .tox-tree .tox-trbtn--labeled{padding:0 4px;width:unset}.tox .tox-tree .tox-trbtn__vlabel{display:block;font-size:10px;font-weight:400;letter-spacing:-.025em;margin-bottom:4px;white-space:nowrap}.tox .tox-tree .tox-tree--directory{display:flex;flex-direction:column}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label{font-weight:700}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-mbtn:focus svg{fill:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:focus .tox-mbtn svg,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover .tox-mbtn svg{fill:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#fff}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label .tox-chevron{margin-right:6px}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--shrinking) .tox-chevron{transition:transform .5s ease-in-out}.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--growing) .tox-chevron,.tox .tox-tree .tox-tree--directory .tox-tree--directory__label:has(+.tox-tree--directory__children--open) .tox-chevron{transform:rotate(90deg)}.tox .tox-tree .tox-tree--leaf__label{font-weight:400}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn{margin-left:auto}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn svg{fill:transparent}.tox .tox-tree .tox-tree--leaf__label .tox-mbtn.tox-mbtn--active svg,.tox .tox-tree .tox-tree--leaf__label .tox-mbtn:focus svg{fill:#fff}.tox .tox-tree .tox-tree--leaf__label:hover .tox-mbtn svg{fill:#fff}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover){background-color:transparent;color:#fff}.tox .tox-tree .tox-tree--leaf__label:hover:has(.tox-mbtn:hover) .tox-chevron svg{fill:#fff}.tox .tox-tree .tox-tree--directory__children{overflow:hidden;padding-left:16px}.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--growing,.tox .tox-tree .tox-tree--directory__children.tox-tree--directory__children--shrinking{transition:height .5s ease-in-out}.tox .tox-tree .tox-trbtn.tox-tree--leaf__label{display:flex;justify-content:space-between}.tox .tox-revisionhistory__pane{padding:0!important}.tox .tox-revisionhistory__container{display:flex;flex-direction:column;height:100%}.tox .tox-revisionhistory{background-color:#2b3b4e;border-radius:4px;border-top:1px solid #000;display:flex;flex:1;height:100%;margin-top:8px;overflow-x:auto;overflow-y:hidden;position:relative;width:100%}.tox .tox-revisionhistory--align-right{margin-left:auto}.tox .tox-revisionhistory__iframe{flex:1}.tox .tox-revisionhistory__sidebar{border-left:1px solid #000;height:100%;max-width:360px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__sidebar-title{border-bottom:1px solid #000;color:#fff;font-size:20px;font-weight:400;height:60px;min-width:192px;padding:16px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions{flex-direction:column;max-height:calc(100% - 60px);min-width:192px;overflow-y:auto;padding:8px}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus{height:100%;position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0;border-radius:3px;bottom:1px;left:1px;right:1px;top:1px}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card{border:1px solid #000;border-radius:3px;color:#fff;cursor:pointer;font-size:14px;margin-bottom:8px;padding:8px;text-overflow:ellipsis;text-wrap:nowrap;width:100%}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:hover{background-color:#4a5562;box-shadow:none;color:#fff}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus{position:relative;z-index:1}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border-radius:3px!important;border-radius:3px;bottom:0;box-shadow:0 0 0 0 transparent;content:'';left:0;position:absolute;right:0;top:0}@media (forced-colors:active){.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card:focus::after{border:2px solid highlight}}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__card.tox-revisionhistory__card--selected{background-color:#6ea9d0;box-shadow:none;color:#fff}.tox .tox-revisionhistory__sidebar .tox-revisionhistory__revisions .tox-revisionhistory__norevision{color:rgba(255,255,255,.5);font-size:16px;line-height:24px;padding:5px 5.5px}.tox .tox-view-wrap,.tox .tox-view-wrap__slot-container{background-color:#222f3e;display:flex;flex:1;flex-direction:column;height:100%}.tox .tox-view{display:flex;flex:1 1 auto;flex-direction:column;overflow:hidden}.tox .tox-view__header{align-items:center;display:flex;font-size:16px;justify-content:space-between;padding:8px 8px 0 8px;position:relative}.tox .tox-view__label{color:#fff;font-weight:700;line-height:24px;padding:4px 16px;text-align:center;white-space:nowrap}.tox .tox-view__label--normal{font-size:16px}.tox .tox-view__label--large{font-size:20px}.tox .tox-view--mobile.tox-view__header,.tox .tox-view--mobile.tox-view__toolbar{padding:8px}.tox .tox-view--scrolling{flex-wrap:nowrap;overflow-x:auto}.tox .tox-view__toolbar{display:flex;flex-direction:row;gap:8px;justify-content:space-between;overflow-x:auto;padding:8px 8px 0 8px}.tox .tox-view__toolbar__group{display:flex;flex-direction:row;gap:12px}.tox .tox-view__header-end,.tox .tox-view__header-start{display:flex}.tox .tox-view__pane{height:100%;padding:8px;position:relative;width:100%}.tox .tox-view__pane_panel{border:1px solid #000;border-radius:3px}.tox:not([dir=rtl]) .tox-view__header .tox-view__header-end>*,.tox:not([dir=rtl]) .tox-view__header .tox-view__header-start>*{margin-left:8px}.tox[dir=rtl] .tox-view__header .tox-view__header-end>*,.tox[dir=rtl] .tox-view__header .tox-view__header-start>*{margin-right:8px}.tox .tox-well{border:1px solid #000;border-radius:3px;padding:8px;width:100%}.tox .tox-well>:first-child{margin-top:0}.tox .tox-well>:last-child{margin-bottom:0}.tox .tox-well>:only-child{margin:0}.tox .tox-custom-editor{border:1px solid #000;border-radius:3px;display:flex;flex:1;overflow:hidden;position:relative}.tox .tox-dialog-loading::before{background-color:rgba(0,0,0,.5);content:\"\";height:100%;position:absolute;width:100%;z-index:1000}.tox .tox-tab{cursor:pointer}.tox .tox-dialog__content-js{display:flex;flex:1}.tox .tox-dialog__body-content .tox-collection{display:flex;flex:1}.tox:not(.tox-tinymce-inline) .tox-editor-header{background-color:none;padding:0}.tox.tox-tinymce--toolbar-bottom .tox-editor-header,.tox.tox-tinymce-inline .tox-editor-header{margin-bottom:-1px}.tox.tox-tinymce-inline .tox-editor-container{overflow:hidden}.tox:not(.tox-tinymce-inline).tox-tinymce--toolbar-bottom .tox-editor-header{border-top:none;box-shadow:none}.tox.tox.tox-tinymce--toolbar-sticky-on .tox-editor-header{background-color:transparent;box-shadow:0 4px 4px -3px rgba(0,0,0,.25);padding:0}.tox.tox.tox-tinymce--toolbar-sticky-on.tox-tinymce--toolbar-bottom .tox-editor-header{box-shadow:0 4px 4px -3px rgba(0,0,0,.25)}.tox .tox-collection--list .tox-collection__group .tox-insert-table-picker{margin:-4px 0}.tox .tox-menu.tox-collection.tox-collection--list{padding:0}.tox .tox-pop{box-shadow:none}.tox .tox-number-input,.tox .tox-split-button,.tox .tox-tbtn,.tox .tox-tbtn--select{margin:2px 0 3px 0}.tox .tox-toolbar,.tox .tox-toolbar__overflow,.tox .tox-toolbar__primary{background:url(\"data:image/svg+xml;charset=utf8,%3Csvg height='39px' viewBox='0 0 40 39px' width='40' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='38px' width='100' height='1' fill='%23000000'/%3E%3C/svg%3E\") left 0 top 0 #222f3e!important}.tox .tox-menubar+.tox-toolbar-overlord{border-top:none}.tox .tox-menubar+.tox-toolbar,.tox .tox-menubar+.tox-toolbar-overlord .tox-toolbar__primary{border-top:1px solid #000;margin-top:-1px}.tox.tox-tinymce-aux .tox-toolbar__overflow{border:1px solid #000;padding:0}.tox .tox-pop .tox-pop__dialog .tox-toolbar{padding:0}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-menubar{border-top:1px solid #000}.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar-overlord:first-child .tox-toolbar__primary,.tox:not(.tox-tinymce-inline) .tox-editor-header:not(:first-child) .tox-toolbar:first-child{border-top:1px solid #000}.tox .tox-toolbar__group{padding:0 4px 0 4px}.tox .tox-collection__item{border-radius:0;cursor:pointer}.tox .tox-statusbar a:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar a:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__path-item:hover:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:focus:not(:disabled):not([aria-disabled=true]),.tox .tox-statusbar__wordcount:hover:not(:disabled):not([aria-disabled=true]){color:#fff}.tox .tox-statusbar__branding svg{fill:rgba(255,255,255,.8);height:1em;margin-left:.3em;width:auto}@media (forced-colors:active){.tox .tox-statusbar__branding svg{fill:currentColor}}.tox .tox-statusbar__branding a{align-items:center;display:inline-flex}.tox .tox-statusbar__branding a:focus:not(:disabled):not([aria-disabled=true]) svg,.tox .tox-statusbar__branding a:hover:not(:disabled):not([aria-disabled=true]) svg{fill:#fff}.tox:not([dir=rtl]) .tox-statusbar__branding{margin-left:1ch}.tox[dir=rtl] .tox-statusbar__branding svg{margin-left:0;margin-right:.3em}.tox .tox-statusbar__resize-handle{padding-bottom:0;padding-right:0}.tox .tox-button::before{display:none}") //# sourceMappingURL=skin.js.map ================================================ FILE: apps/web-antd/public/tinymce/skins/ui/tinymce-5-dark/skin.shadowdom.js ================================================ tinymce.Resource.add('ui/tinymce-5-dark/skin.shadowdom.css', "body.tox-dialog__disable-scroll{overflow:hidden}.tox-fullscreen{border:0;height:100%;margin:0;overflow:hidden;overscroll-behavior:none;padding:0;touch-action:pinch-zoom;width:100%}.tox.tox-tinymce.tox-fullscreen .tox-statusbar__resize-handle{display:none}.tox-shadowhost.tox-fullscreen,.tox.tox-tinymce.tox-fullscreen{left:0;position:fixed;top:0;z-index:1200}.tox.tox-tinymce.tox-fullscreen{background-color:transparent}.tox-fullscreen .tox.tox-tinymce-aux,.tox-fullscreen~.tox.tox-tinymce-aux{z-index:1201}") //# sourceMappingURL=skin.shadowdom.js.map ================================================ FILE: apps/web-antd/public/tinymce/tinymce.d.ts ================================================ interface StringPathBookmark { start: string; end?: string; forward?: boolean; } interface RangeBookmark { rng: Range; forward?: boolean; } interface IdBookmark { id: string; keep?: boolean; forward?: boolean; } interface IndexBookmark { name: string; index: number; } interface PathBookmark { start: number[]; end?: number[]; isFakeCaret?: boolean; forward?: boolean; } type Bookmark = StringPathBookmark | RangeBookmark | IdBookmark | IndexBookmark | PathBookmark; type NormalizedEvent = E & { readonly type: string; readonly target: T; readonly isDefaultPrevented: () => boolean; readonly preventDefault: () => void; readonly isPropagationStopped: () => boolean; readonly stopPropagation: () => void; readonly isImmediatePropagationStopped: () => boolean; readonly stopImmediatePropagation: () => void; }; type MappedEvent = K extends keyof T ? T[K] : any; interface NativeEventMap { 'beforepaste': Event; 'blur': FocusEvent; 'beforeinput': InputEvent; 'click': MouseEvent; 'compositionend': Event; 'compositionstart': Event; 'compositionupdate': Event; 'contextmenu': PointerEvent; 'copy': ClipboardEvent; 'cut': ClipboardEvent; 'dblclick': MouseEvent; 'drag': DragEvent; 'dragdrop': DragEvent; 'dragend': DragEvent; 'draggesture': DragEvent; 'dragover': DragEvent; 'dragstart': DragEvent; 'drop': DragEvent; 'focus': FocusEvent; 'focusin': FocusEvent; 'focusout': FocusEvent; 'input': InputEvent; 'keydown': KeyboardEvent; 'keypress': KeyboardEvent; 'keyup': KeyboardEvent; 'mousedown': MouseEvent; 'mouseenter': MouseEvent; 'mouseleave': MouseEvent; 'mousemove': MouseEvent; 'mouseout': MouseEvent; 'mouseover': MouseEvent; 'mouseup': MouseEvent; 'paste': ClipboardEvent; 'selectionchange': Event; 'submit': Event; 'touchend': TouchEvent; 'touchmove': TouchEvent; 'touchstart': TouchEvent; 'touchcancel': TouchEvent; 'wheel': WheelEvent; } type EditorEvent = NormalizedEvent; interface EventDispatcherSettings { scope?: any; toggleEvent?: (name: string, state: boolean) => void | boolean; beforeFire?: (args: EditorEvent) => void; } interface EventDispatcherConstructor { readonly prototype: EventDispatcher; new (settings?: EventDispatcherSettings): EventDispatcher; isNative: (name: string) => boolean; } declare class EventDispatcher { static isNative(name: string): boolean; private readonly settings; private readonly scope; private readonly toggleEvent; private bindings; constructor(settings?: EventDispatcherSettings); fire>(name: K, args?: U): EditorEvent; dispatch>(name: K, args?: U): EditorEvent; on(name: K, callback: false | ((event: EditorEvent>) => void | boolean), prepend?: boolean, extra?: {}): this; off(name?: K, callback?: (event: EditorEvent>) => void): this; once(name: K, callback: (event: EditorEvent>) => void, prepend?: boolean): this; has(name: string): boolean; } type UndoLevelType = 'fragmented' | 'complete'; interface BaseUndoLevel { type: UndoLevelType; bookmark: Bookmark | null; beforeBookmark: Bookmark | null; } interface FragmentedUndoLevel extends BaseUndoLevel { type: 'fragmented'; fragments: string[]; content: ''; } interface CompleteUndoLevel extends BaseUndoLevel { type: 'complete'; fragments: null; content: string; } type NewUndoLevel = CompleteUndoLevel | FragmentedUndoLevel; type UndoLevel = NewUndoLevel & { bookmark: Bookmark; }; interface UndoManager { data: UndoLevel[]; typing: boolean; add: (level?: Partial, event?: EditorEvent) => UndoLevel | null; dispatchChange: () => void; beforeChange: () => void; undo: () => UndoLevel | undefined; redo: () => UndoLevel | undefined; clear: () => void; reset: () => void; hasUndo: () => boolean; hasRedo: () => boolean; transact: (callback: () => void) => UndoLevel | null; ignore: (callback: () => void) => void; extra: (callback1: () => void, callback2: () => void) => void; } type SchemaType = 'html4' | 'html5' | 'html5-strict'; interface ElementSettings { block_elements?: string; boolean_attributes?: string; move_caret_before_on_enter_elements?: string; non_empty_elements?: string; self_closing_elements?: string; text_block_elements?: string; text_inline_elements?: string; void_elements?: string; whitespace_elements?: string; transparent_elements?: string; wrap_block_elements?: string; } interface SchemaSettings extends ElementSettings { custom_elements?: string | Record; extended_valid_elements?: string; invalid_elements?: string; invalid_styles?: string | Record; schema?: SchemaType; valid_children?: string; valid_classes?: string | Record; valid_elements?: string; valid_styles?: string | Record; verify_html?: boolean; padd_empty_block_inline_children?: boolean; } interface Attribute { required?: boolean; defaultValue?: string; forcedValue?: string; validValues?: Record; } interface DefaultAttribute { name: string; value: string; } interface AttributePattern extends Attribute { pattern: RegExp; } interface ElementRule { attributes: Record; attributesDefault?: DefaultAttribute[]; attributesForced?: DefaultAttribute[]; attributesOrder: string[]; attributePatterns?: AttributePattern[]; attributesRequired?: string[]; paddEmpty?: boolean; removeEmpty?: boolean; removeEmptyAttrs?: boolean; paddInEmptyBlock?: boolean; } interface SchemaElement extends ElementRule { outputName?: string; parentsRequired?: string[]; pattern?: RegExp; } interface SchemaMap { [name: string]: {}; } interface SchemaRegExpMap { [name: string]: RegExp; } interface CustomElementSpec { extends?: string; attributes?: string[]; children?: string[]; padEmpty?: boolean; } interface Schema { type: SchemaType; children: Record; elements: Record; getValidStyles: () => Record | undefined; getValidClasses: () => Record | undefined; getBlockElements: () => SchemaMap; getInvalidStyles: () => Record | undefined; getVoidElements: () => SchemaMap; getTextBlockElements: () => SchemaMap; getTextInlineElements: () => SchemaMap; getBoolAttrs: () => SchemaMap; getElementRule: (name: string) => SchemaElement | undefined; getSelfClosingElements: () => SchemaMap; getNonEmptyElements: () => SchemaMap; getMoveCaretBeforeOnEnterElements: () => SchemaMap; getWhitespaceElements: () => SchemaMap; getTransparentElements: () => SchemaMap; getSpecialElements: () => SchemaRegExpMap; isValidChild: (name: string, child: string) => boolean; isValid: (name: string, attr?: string) => boolean; isBlock: (name: string) => boolean; isInline: (name: string) => boolean; isWrapper: (name: string) => boolean; getCustomElements: () => SchemaMap; addValidElements: (validElements: string) => void; setValidElements: (validElements: string) => void; addCustomElements: (customElements: string | Record) => void; addValidChildren: (validChildren: any) => void; } type Attributes$1 = Array<{ name: string; value: string; }> & { map: Record; }; interface AstNodeConstructor { readonly prototype: AstNode; new (name: string, type: number): AstNode; create(name: string, attrs?: Record): AstNode; } declare class AstNode { static create(name: string, attrs?: Record): AstNode; name: string; type: number; attributes?: Attributes$1; value?: string; parent?: AstNode | null; firstChild?: AstNode | null; lastChild?: AstNode | null; next?: AstNode | null; prev?: AstNode | null; raw?: boolean; constructor(name: string, type: number); replace(node: AstNode): AstNode; attr(name: string, value: string | null | undefined): AstNode | undefined; attr(name: Record | undefined): AstNode | undefined; attr(name: string): string | undefined; clone(): AstNode; wrap(wrapper: AstNode): AstNode; unwrap(): void; remove(): AstNode; append(node: AstNode): AstNode; insert(node: AstNode, refNode: AstNode, before?: boolean): AstNode; getAll(name: string): AstNode[]; children(): AstNode[]; empty(): AstNode; isEmpty(elements: SchemaMap, whitespace?: SchemaMap, predicate?: (node: AstNode) => boolean): boolean; walk(prev?: boolean): AstNode | null | undefined; } type Content = string | AstNode; type ContentFormat = 'raw' | 'text' | 'html' | 'tree'; interface GetContentArgs { format: ContentFormat; get: boolean; getInner: boolean; no_events?: boolean; save?: boolean; source_view?: boolean; [key: string]: any; } interface SetContentArgs { format: string; set: boolean; content: Content; no_events?: boolean; no_selection?: boolean; paste?: boolean; load?: boolean; initial?: boolean; [key: string]: any; } interface GetSelectionContentArgs extends GetContentArgs { selection?: boolean; contextual?: boolean; } interface SetSelectionContentArgs extends SetContentArgs { content: string; selection?: boolean; } interface BlobInfoData { id?: string; name?: string; filename?: string; blob: Blob; base64: string; blobUri?: string; uri?: string; } interface BlobInfo { id: () => string; name: () => string; filename: () => string; blob: () => Blob; base64: () => string; blobUri: () => string; uri: () => string | undefined; } interface BlobCache { create: { (o: BlobInfoData): BlobInfo; (id: string, blob: Blob, base64: string, name?: string, filename?: string): BlobInfo; }; add: (blobInfo: BlobInfo) => void; get: (id: string) => BlobInfo | undefined; getByUri: (blobUri: string) => BlobInfo | undefined; getByData: (base64: string, type: string) => BlobInfo | undefined; findFirst: (predicate: (blobInfo: BlobInfo) => boolean) => BlobInfo | undefined; removeByUri: (blobUri: string) => void; destroy: () => void; } interface BlobInfoImagePair { image: HTMLImageElement; blobInfo: BlobInfo; } declare class NodeChange { private readonly editor; private lastPath; constructor(editor: Editor); nodeChanged(args?: Record): void; private isSameElementPath; } interface SelectionOverrides { showCaret: (direction: number, node: HTMLElement, before: boolean, scrollIntoView?: boolean) => Range | null; showBlockCaretContainer: (blockCaretContainer: HTMLElement) => void; hideFakeCaret: () => void; destroy: () => void; } interface Quirks { refreshContentEditable(): void; isHidden(): boolean; } type DecoratorData = Record; type Decorator = (uid: string, data: DecoratorData) => { attributes?: {}; classes?: string[]; }; type AnnotationListener = (state: boolean, name: string, data?: { uid: string; nodes: any[]; }) => void; type AnnotationListenerApi = AnnotationListener; interface AnnotatorSettings { decorate: Decorator; persistent?: boolean; } interface Annotator { register: (name: string, settings: AnnotatorSettings) => void; annotate: (name: string, data: DecoratorData) => void; annotationChanged: (name: string, f: AnnotationListenerApi) => void; remove: (name: string) => void; removeAll: (name: string) => void; getAll: (name: string) => Record; } interface IsEmptyOptions { readonly skipBogus?: boolean; readonly includeZwsp?: boolean; readonly checkRootAsContent?: boolean; readonly isContent?: (node: Node) => boolean; } interface GeomRect { readonly x: number; readonly y: number; readonly w: number; readonly h: number; } interface Rect { inflate: (rect: GeomRect, w: number, h: number) => GeomRect; relativePosition: (rect: GeomRect, targetRect: GeomRect, rel: string) => GeomRect; findBestRelativePosition: (rect: GeomRect, targetRect: GeomRect, constrainRect: GeomRect, rels: string[]) => string | null; intersect: (rect: GeomRect, cropRect: GeomRect) => GeomRect | null; clamp: (rect: GeomRect, clampRect: GeomRect, fixedSize?: boolean) => GeomRect; create: (x: number, y: number, w: number, h: number) => GeomRect; fromClientRect: (clientRect: DOMRect) => GeomRect; } interface NotificationManagerImpl { open: (spec: NotificationSpec, closeCallback: () => void, hasEditorFocus: () => boolean) => NotificationApi; close: (notification: T) => void; getArgs: (notification: T) => NotificationSpec; } interface NotificationSpec { type?: 'info' | 'warning' | 'error' | 'success'; text: string; icon?: string; progressBar?: boolean; timeout?: number; } interface NotificationApi { close: () => void; progressBar: { value: (percent: number) => void; }; text: (text: string) => void; reposition: () => void; getEl: () => HTMLElement; settings: NotificationSpec; } interface NotificationManager { open: (spec: NotificationSpec) => NotificationApi; close: () => void; getNotifications: () => NotificationApi[]; } interface UploadFailure { message: string; remove?: boolean; } type ProgressFn = (percent: number) => void; type UploadHandler = (blobInfo: BlobInfo, progress: ProgressFn) => Promise; interface UploadResult$2 { url: string; blobInfo: BlobInfo; status: boolean; error?: UploadFailure; } type BlockPatternTrigger = 'enter' | 'space'; interface RawPattern { start?: any; end?: any; format?: any; cmd?: any; value?: any; replacement?: any; trigger?: BlockPatternTrigger; } interface InlineBasePattern { readonly start: string; readonly end: string; } interface InlineFormatPattern extends InlineBasePattern { readonly type: 'inline-format'; readonly format: string[]; } interface InlineCmdPattern extends InlineBasePattern { readonly type: 'inline-command'; readonly cmd: string; readonly value?: any; } type InlinePattern = InlineFormatPattern | InlineCmdPattern; interface BlockBasePattern { readonly start: string; readonly trigger: BlockPatternTrigger; } interface BlockFormatPattern extends BlockBasePattern { readonly type: 'block-format'; readonly format: string; } interface BlockCmdPattern extends BlockBasePattern { readonly type: 'block-command'; readonly cmd: string; readonly value?: any; } type BlockPattern = BlockFormatPattern | BlockCmdPattern; type Pattern = InlinePattern | BlockPattern; interface DynamicPatternContext { readonly text: string; readonly block: Element; } type DynamicPatternsLookup = (ctx: DynamicPatternContext) => Pattern[]; type RawDynamicPatternsLookup = (ctx: DynamicPatternContext) => RawPattern[]; interface AlertBannerSpec { type: 'alertbanner'; level: 'info' | 'warn' | 'error' | 'success'; text: string; icon: string; url?: string; } interface ButtonSpec { type: 'button'; text: string; enabled?: boolean; primary?: boolean; name?: string; icon?: string; borderless?: boolean; buttonType?: 'primary' | 'secondary' | 'toolbar'; } interface FormComponentSpec { type: string; name: string; } interface FormComponentWithLabelSpec extends FormComponentSpec { label?: string; } interface CheckboxSpec extends FormComponentSpec { type: 'checkbox'; label: string; enabled?: boolean; } interface CollectionSpec extends FormComponentWithLabelSpec { type: 'collection'; } interface CollectionItem { value: string; text: string; icon: string; } interface ColorInputSpec extends FormComponentWithLabelSpec { type: 'colorinput'; storageKey?: string; } interface ColorPickerSpec extends FormComponentWithLabelSpec { type: 'colorpicker'; } interface CustomEditorInit { setValue: (value: string) => void; getValue: () => string; destroy: () => void; } type CustomEditorInitFn = (elm: HTMLElement, settings: any) => Promise; interface CustomEditorOldSpec extends FormComponentSpec { type: 'customeditor'; tag?: string; init: (e: HTMLElement) => Promise; } interface CustomEditorNewSpec extends FormComponentSpec { type: 'customeditor'; tag?: string; scriptId: string; scriptUrl: string; onFocus?: (e: HTMLElement) => void; settings?: any; } type CustomEditorSpec = CustomEditorOldSpec | CustomEditorNewSpec; interface DropZoneSpec extends FormComponentWithLabelSpec { type: 'dropzone'; } interface GridSpec { type: 'grid'; columns: number; items: BodyComponentSpec[]; } interface HtmlPanelSpec { type: 'htmlpanel'; html: string; onInit?: (el: HTMLElement) => void; presets?: 'presentation' | 'document'; stretched?: boolean; } interface IframeSpec extends FormComponentWithLabelSpec { type: 'iframe'; border?: boolean; sandboxed?: boolean; streamContent?: boolean; transparent?: boolean; } interface ImagePreviewSpec extends FormComponentSpec { type: 'imagepreview'; height?: string; } interface InputSpec extends FormComponentWithLabelSpec { type: 'input'; inputMode?: string; placeholder?: string; maximized?: boolean; enabled?: boolean; } type Alignment = 'start' | 'center' | 'end'; interface LabelSpec { type: 'label'; label: string; items: BodyComponentSpec[]; align?: Alignment; for?: string; } interface ListBoxSingleItemSpec { text: string; value: string; } interface ListBoxNestedItemSpec { text: string; items: ListBoxItemSpec[]; } type ListBoxItemSpec = ListBoxNestedItemSpec | ListBoxSingleItemSpec; interface ListBoxSpec extends FormComponentWithLabelSpec { type: 'listbox'; items: ListBoxItemSpec[]; disabled?: boolean; } interface PanelSpec { type: 'panel'; classes?: string[]; items: BodyComponentSpec[]; } interface SelectBoxItemSpec { text: string; value: string; } interface SelectBoxSpec extends FormComponentWithLabelSpec { type: 'selectbox'; items: SelectBoxItemSpec[]; size?: number; enabled?: boolean; } interface SizeInputSpec extends FormComponentWithLabelSpec { type: 'sizeinput'; constrain?: boolean; enabled?: boolean; } interface SliderSpec extends FormComponentSpec { type: 'slider'; label: string; min?: number; max?: number; } interface TableSpec { type: 'table'; header: string[]; cells: string[][]; } interface TextAreaSpec extends FormComponentWithLabelSpec { type: 'textarea'; placeholder?: string; maximized?: boolean; enabled?: boolean; } interface BaseToolbarButtonSpec { enabled?: boolean; tooltip?: string; icon?: string; text?: string; onSetup?: (api: I) => (api: I) => void; } interface BaseToolbarButtonInstanceApi { isEnabled: () => boolean; setEnabled: (state: boolean) => void; setText: (text: string) => void; setIcon: (icon: string) => void; } interface ToolbarButtonSpec extends BaseToolbarButtonSpec { type?: 'button'; onAction: (api: ToolbarButtonInstanceApi) => void; shortcut?: string; } interface ToolbarButtonInstanceApi extends BaseToolbarButtonInstanceApi { } interface ToolbarGroupSetting { name: string; items: string[]; } type ToolbarConfig = string | ToolbarGroupSetting[]; interface GroupToolbarButtonInstanceApi extends BaseToolbarButtonInstanceApi { } interface GroupToolbarButtonSpec extends BaseToolbarButtonSpec { type?: 'grouptoolbarbutton'; items?: ToolbarConfig; } interface CardImageSpec { type: 'cardimage'; src: string; alt?: string; classes?: string[]; } interface CardTextSpec { type: 'cardtext'; text: string; name?: string; classes?: string[]; } type CardItemSpec = CardContainerSpec | CardImageSpec | CardTextSpec; type CardContainerDirection = 'vertical' | 'horizontal'; type CardContainerAlign = 'left' | 'right'; type CardContainerValign = 'top' | 'middle' | 'bottom'; interface CardContainerSpec { type: 'cardcontainer'; items: CardItemSpec[]; direction?: CardContainerDirection; align?: CardContainerAlign; valign?: CardContainerValign; } interface CommonMenuItemSpec { enabled?: boolean; text?: string; value?: string; meta?: Record; shortcut?: string; } interface CommonMenuItemInstanceApi { isEnabled: () => boolean; setEnabled: (state: boolean) => void; } interface CardMenuItemInstanceApi extends CommonMenuItemInstanceApi { } interface CardMenuItemSpec extends Omit { type: 'cardmenuitem'; label?: string; items: CardItemSpec[]; onSetup?: (api: CardMenuItemInstanceApi) => (api: CardMenuItemInstanceApi) => void; onAction?: (api: CardMenuItemInstanceApi) => void; } interface ChoiceMenuItemSpec extends CommonMenuItemSpec { type?: 'choiceitem'; icon?: string; } interface ChoiceMenuItemInstanceApi extends CommonMenuItemInstanceApi { isActive: () => boolean; setActive: (state: boolean) => void; } interface ContextMenuItem extends CommonMenuItemSpec { text: string; icon?: string; type?: 'item'; onAction: () => void; } interface ContextSubMenu extends CommonMenuItemSpec { type: 'submenu'; text: string; icon?: string; getSubmenuItems: () => string | Array; } type ContextMenuContents = string | ContextMenuItem | SeparatorMenuItemSpec | ContextSubMenu; interface ContextMenuApi { update: (element: Element) => string | Array; } interface FancyActionArgsMap { 'inserttable': { numRows: number; numColumns: number; }; 'colorswatch': { value: string; }; } interface BaseFancyMenuItemSpec { type: 'fancymenuitem'; fancytype: T; initData?: Record; onAction?: (data: FancyActionArgsMap[T]) => void; } interface InsertTableMenuItemSpec extends BaseFancyMenuItemSpec<'inserttable'> { fancytype: 'inserttable'; initData?: {}; } interface ColorSwatchMenuItemSpec extends BaseFancyMenuItemSpec<'colorswatch'> { fancytype: 'colorswatch'; select?: (value: string) => boolean; initData?: { allowCustomColors?: boolean; colors?: ChoiceMenuItemSpec[]; storageKey?: string; }; } type FancyMenuItemSpec = InsertTableMenuItemSpec | ColorSwatchMenuItemSpec; interface MenuItemSpec extends CommonMenuItemSpec { type?: 'menuitem'; icon?: string; onSetup?: (api: MenuItemInstanceApi) => (api: MenuItemInstanceApi) => void; onAction?: (api: MenuItemInstanceApi) => void; } interface MenuItemInstanceApi extends CommonMenuItemInstanceApi { } interface SeparatorMenuItemSpec { type?: 'separator'; text?: string; } interface ToggleMenuItemSpec extends CommonMenuItemSpec { type?: 'togglemenuitem'; icon?: string; active?: boolean; onSetup?: (api: ToggleMenuItemInstanceApi) => void; onAction: (api: ToggleMenuItemInstanceApi) => void; } interface ToggleMenuItemInstanceApi extends CommonMenuItemInstanceApi { isActive: () => boolean; setActive: (state: boolean) => void; } type NestedMenuItemContents = string | MenuItemSpec | NestedMenuItemSpec | ToggleMenuItemSpec | SeparatorMenuItemSpec | FancyMenuItemSpec; interface NestedMenuItemSpec extends CommonMenuItemSpec { type?: 'nestedmenuitem'; icon?: string; getSubmenuItems: () => string | Array; onSetup?: (api: NestedMenuItemInstanceApi) => (api: NestedMenuItemInstanceApi) => void; } interface NestedMenuItemInstanceApi extends CommonMenuItemInstanceApi { setTooltip: (tooltip: string) => void; setIconFill: (id: string, value: string) => void; } type MenuButtonItemTypes = NestedMenuItemContents; type SuccessCallback$1 = (menu: string | MenuButtonItemTypes[]) => void; interface MenuButtonFetchContext { pattern: string; } interface BaseMenuButtonSpec { text?: string; tooltip?: string; icon?: string; search?: boolean | { placeholder?: string; }; fetch: (success: SuccessCallback$1, fetchContext: MenuButtonFetchContext, api: BaseMenuButtonInstanceApi) => void; onSetup?: (api: BaseMenuButtonInstanceApi) => (api: BaseMenuButtonInstanceApi) => void; } interface BaseMenuButtonInstanceApi { isEnabled: () => boolean; setEnabled: (state: boolean) => void; isActive: () => boolean; setActive: (state: boolean) => void; setText: (text: string) => void; setIcon: (icon: string) => void; } interface ToolbarMenuButtonSpec extends BaseMenuButtonSpec { type?: 'menubutton'; onSetup?: (api: ToolbarMenuButtonInstanceApi) => (api: ToolbarMenuButtonInstanceApi) => void; } interface ToolbarMenuButtonInstanceApi extends BaseMenuButtonInstanceApi { } type ToolbarSplitButtonItemTypes = ChoiceMenuItemSpec | SeparatorMenuItemSpec; type SuccessCallback = (menu: ToolbarSplitButtonItemTypes[]) => void; type SelectPredicate = (value: string) => boolean; type PresetTypes = 'color' | 'normal' | 'listpreview'; type ColumnTypes$1 = number | 'auto'; interface ToolbarSplitButtonSpec { type?: 'splitbutton'; tooltip?: string; icon?: string; text?: string; select?: SelectPredicate; presets?: PresetTypes; columns?: ColumnTypes$1; fetch: (success: SuccessCallback) => void; onSetup?: (api: ToolbarSplitButtonInstanceApi) => (api: ToolbarSplitButtonInstanceApi) => void; onAction: (api: ToolbarSplitButtonInstanceApi) => void; onItemAction: (api: ToolbarSplitButtonInstanceApi, value: string) => void; } interface ToolbarSplitButtonInstanceApi { isEnabled: () => boolean; setEnabled: (state: boolean) => void; setIconFill: (id: string, value: string) => void; isActive: () => boolean; setActive: (state: boolean) => void; setTooltip: (tooltip: string) => void; setText: (text: string) => void; setIcon: (icon: string) => void; } interface BaseToolbarToggleButtonSpec extends BaseToolbarButtonSpec { active?: boolean; } interface BaseToolbarToggleButtonInstanceApi extends BaseToolbarButtonInstanceApi { isActive: () => boolean; setActive: (state: boolean) => void; } interface ToolbarToggleButtonSpec extends BaseToolbarToggleButtonSpec { type?: 'togglebutton'; onAction: (api: ToolbarToggleButtonInstanceApi) => void; shortcut?: string; } interface ToolbarToggleButtonInstanceApi extends BaseToolbarToggleButtonInstanceApi { } type Id = string; interface TreeSpec { type: 'tree'; items: TreeItemSpec[]; onLeafAction?: (id: Id) => void; defaultExpandedIds?: Id[]; onToggleExpand?: (expandedIds: Id[], { expanded, node }: { expanded: boolean; node: Id; }) => void; defaultSelectedId?: Id; } interface BaseTreeItemSpec { title: string; id: Id; menu?: ToolbarMenuButtonSpec; } interface DirectorySpec extends BaseTreeItemSpec { type: 'directory'; children: TreeItemSpec[]; } interface LeafSpec extends BaseTreeItemSpec { type: 'leaf'; } type TreeItemSpec = DirectorySpec | LeafSpec; interface UrlInputSpec extends FormComponentWithLabelSpec { type: 'urlinput'; filetype?: 'image' | 'media' | 'file'; enabled?: boolean; picker_text?: string; } interface UrlInputData { value: string; meta: { text?: string; }; } type BodyComponentSpec = BarSpec | ButtonSpec | CheckboxSpec | TextAreaSpec | InputSpec | ListBoxSpec | SelectBoxSpec | SizeInputSpec | SliderSpec | IframeSpec | HtmlPanelSpec | UrlInputSpec | DropZoneSpec | ColorInputSpec | GridSpec | ColorPickerSpec | ImagePreviewSpec | AlertBannerSpec | CollectionSpec | LabelSpec | TableSpec | TreeSpec | PanelSpec | CustomEditorSpec; interface BarSpec { type: 'bar'; items: BodyComponentSpec[]; } interface DialogToggleMenuItemSpec extends CommonMenuItemSpec { type?: 'togglemenuitem'; name: string; } type DialogFooterMenuButtonItemSpec = DialogToggleMenuItemSpec; interface BaseDialogFooterButtonSpec { name?: string; align?: 'start' | 'end'; primary?: boolean; enabled?: boolean; icon?: string; buttonType?: 'primary' | 'secondary'; } interface DialogFooterNormalButtonSpec extends BaseDialogFooterButtonSpec { type: 'submit' | 'cancel' | 'custom'; text: string; } interface DialogFooterMenuButtonSpec extends BaseDialogFooterButtonSpec { type: 'menu'; text?: string; tooltip?: string; icon?: string; items: DialogFooterMenuButtonItemSpec[]; } interface DialogFooterToggleButtonSpec extends BaseDialogFooterButtonSpec { type: 'togglebutton'; tooltip?: string; icon?: string; text?: string; active?: boolean; } type DialogFooterButtonSpec = DialogFooterNormalButtonSpec | DialogFooterMenuButtonSpec | DialogFooterToggleButtonSpec; interface TabSpec { name?: string; title: string; items: BodyComponentSpec[]; } interface TabPanelSpec { type: 'tabpanel'; tabs: TabSpec[]; } type DialogDataItem = any; type DialogData = Record; interface DialogInstanceApi { getData: () => T; setData: (data: Partial) => void; setEnabled: (name: string, state: boolean) => void; focus: (name: string) => void; showTab: (name: string) => void; redial: (nu: DialogSpec) => void; block: (msg: string) => void; unblock: () => void; toggleFullscreen: () => void; close: () => void; } interface DialogActionDetails { name: string; value?: any; } interface DialogChangeDetails { name: keyof T; } interface DialogTabChangeDetails { newTabName: string; oldTabName: string; } type DialogActionHandler = (api: DialogInstanceApi, details: DialogActionDetails) => void; type DialogChangeHandler = (api: DialogInstanceApi, details: DialogChangeDetails) => void; type DialogSubmitHandler = (api: DialogInstanceApi) => void; type DialogCloseHandler = () => void; type DialogCancelHandler = (api: DialogInstanceApi) => void; type DialogTabChangeHandler = (api: DialogInstanceApi, details: DialogTabChangeDetails) => void; type DialogSize = 'normal' | 'medium' | 'large'; interface DialogSpec { title: string; size?: DialogSize; body: TabPanelSpec | PanelSpec; buttons?: DialogFooterButtonSpec[]; initialData?: Partial; onAction?: DialogActionHandler; onChange?: DialogChangeHandler; onSubmit?: DialogSubmitHandler; onClose?: DialogCloseHandler; onCancel?: DialogCancelHandler; onTabChange?: DialogTabChangeHandler; } interface UrlDialogInstanceApi { block: (msg: string) => void; unblock: () => void; close: () => void; sendMessage: (msg: any) => void; } interface UrlDialogActionDetails { name: string; value?: any; } interface UrlDialogMessage { mceAction: string; [key: string]: any; } type UrlDialogActionHandler = (api: UrlDialogInstanceApi, actions: UrlDialogActionDetails) => void; type UrlDialogCloseHandler = () => void; type UrlDialogCancelHandler = (api: UrlDialogInstanceApi) => void; type UrlDialogMessageHandler = (api: UrlDialogInstanceApi, message: UrlDialogMessage) => void; interface UrlDialogFooterButtonSpec extends DialogFooterNormalButtonSpec { type: 'cancel' | 'custom'; } interface UrlDialogSpec { title: string; url: string; height?: number; width?: number; buttons?: UrlDialogFooterButtonSpec[]; onAction?: UrlDialogActionHandler; onClose?: UrlDialogCloseHandler; onCancel?: UrlDialogCancelHandler; onMessage?: UrlDialogMessageHandler; } type ColumnTypes = number | 'auto'; type SeparatorItemSpec = SeparatorMenuItemSpec; interface AutocompleterItemSpec { type?: 'autocompleteitem'; value: string; text?: string; icon?: string; meta?: Record; } type AutocompleterContents = SeparatorItemSpec | AutocompleterItemSpec | CardMenuItemSpec; interface AutocompleterSpec { type?: 'autocompleter'; trigger: string; minChars?: number; columns?: ColumnTypes; matches?: (rng: Range, text: string, pattern: string) => boolean; fetch: (pattern: string, maxResults: number, fetchOptions: Record) => Promise; onAction: (autocompleterApi: AutocompleterInstanceApi, rng: Range, value: string, meta: Record) => void; maxResults?: number; highlightOn?: string[]; } interface AutocompleterInstanceApi { hide: () => void; reload: (fetchOptions: Record) => void; } type ContextPosition = 'node' | 'selection' | 'line'; type ContextScope = 'node' | 'editor'; interface ContextBarSpec { predicate?: (elem: Element) => boolean; position?: ContextPosition; scope?: ContextScope; } interface ContextFormLaunchButtonApi extends BaseToolbarButtonSpec { type: 'contextformbutton'; } interface ContextFormLaunchToggleButtonSpec extends BaseToolbarToggleButtonSpec { type: 'contextformtogglebutton'; } interface ContextFormButtonInstanceApi extends BaseToolbarButtonInstanceApi { } interface ContextFormToggleButtonInstanceApi extends BaseToolbarToggleButtonInstanceApi { } interface ContextFormButtonSpec extends BaseToolbarButtonSpec { type?: 'contextformbutton'; primary?: boolean; onAction: (formApi: ContextFormInstanceApi, api: ContextFormButtonInstanceApi) => void; } interface ContextFormToggleButtonSpec extends BaseToolbarToggleButtonSpec { type?: 'contextformtogglebutton'; onAction: (formApi: ContextFormInstanceApi, buttonApi: ContextFormToggleButtonInstanceApi) => void; primary?: boolean; } interface ContextFormInstanceApi { hide: () => void; getValue: () => string; } interface ContextFormSpec extends ContextBarSpec { type?: 'contextform'; initValue?: () => string; label?: string; launch?: ContextFormLaunchButtonApi | ContextFormLaunchToggleButtonSpec; commands: Array; } interface ContextToolbarSpec extends ContextBarSpec { type?: 'contexttoolbar'; items: string; } type PublicDialog_d_AlertBannerSpec = AlertBannerSpec; type PublicDialog_d_BarSpec = BarSpec; type PublicDialog_d_BodyComponentSpec = BodyComponentSpec; type PublicDialog_d_ButtonSpec = ButtonSpec; type PublicDialog_d_CheckboxSpec = CheckboxSpec; type PublicDialog_d_CollectionItem = CollectionItem; type PublicDialog_d_CollectionSpec = CollectionSpec; type PublicDialog_d_ColorInputSpec = ColorInputSpec; type PublicDialog_d_ColorPickerSpec = ColorPickerSpec; type PublicDialog_d_CustomEditorSpec = CustomEditorSpec; type PublicDialog_d_CustomEditorInit = CustomEditorInit; type PublicDialog_d_CustomEditorInitFn = CustomEditorInitFn; type PublicDialog_d_DialogData = DialogData; type PublicDialog_d_DialogSize = DialogSize; type PublicDialog_d_DialogSpec = DialogSpec; type PublicDialog_d_DialogInstanceApi = DialogInstanceApi; type PublicDialog_d_DialogFooterButtonSpec = DialogFooterButtonSpec; type PublicDialog_d_DialogActionDetails = DialogActionDetails; type PublicDialog_d_DialogChangeDetails = DialogChangeDetails; type PublicDialog_d_DialogTabChangeDetails = DialogTabChangeDetails; type PublicDialog_d_DropZoneSpec = DropZoneSpec; type PublicDialog_d_GridSpec = GridSpec; type PublicDialog_d_HtmlPanelSpec = HtmlPanelSpec; type PublicDialog_d_IframeSpec = IframeSpec; type PublicDialog_d_ImagePreviewSpec = ImagePreviewSpec; type PublicDialog_d_InputSpec = InputSpec; type PublicDialog_d_LabelSpec = LabelSpec; type PublicDialog_d_ListBoxSpec = ListBoxSpec; type PublicDialog_d_ListBoxItemSpec = ListBoxItemSpec; type PublicDialog_d_ListBoxNestedItemSpec = ListBoxNestedItemSpec; type PublicDialog_d_ListBoxSingleItemSpec = ListBoxSingleItemSpec; type PublicDialog_d_PanelSpec = PanelSpec; type PublicDialog_d_SelectBoxSpec = SelectBoxSpec; type PublicDialog_d_SelectBoxItemSpec = SelectBoxItemSpec; type PublicDialog_d_SizeInputSpec = SizeInputSpec; type PublicDialog_d_SliderSpec = SliderSpec; type PublicDialog_d_TableSpec = TableSpec; type PublicDialog_d_TabSpec = TabSpec; type PublicDialog_d_TabPanelSpec = TabPanelSpec; type PublicDialog_d_TextAreaSpec = TextAreaSpec; type PublicDialog_d_TreeSpec = TreeSpec; type PublicDialog_d_TreeItemSpec = TreeItemSpec; type PublicDialog_d_UrlInputData = UrlInputData; type PublicDialog_d_UrlInputSpec = UrlInputSpec; type PublicDialog_d_UrlDialogSpec = UrlDialogSpec; type PublicDialog_d_UrlDialogFooterButtonSpec = UrlDialogFooterButtonSpec; type PublicDialog_d_UrlDialogInstanceApi = UrlDialogInstanceApi; type PublicDialog_d_UrlDialogActionDetails = UrlDialogActionDetails; type PublicDialog_d_UrlDialogMessage = UrlDialogMessage; declare namespace PublicDialog_d { export { PublicDialog_d_AlertBannerSpec as AlertBannerSpec, PublicDialog_d_BarSpec as BarSpec, PublicDialog_d_BodyComponentSpec as BodyComponentSpec, PublicDialog_d_ButtonSpec as ButtonSpec, PublicDialog_d_CheckboxSpec as CheckboxSpec, PublicDialog_d_CollectionItem as CollectionItem, PublicDialog_d_CollectionSpec as CollectionSpec, PublicDialog_d_ColorInputSpec as ColorInputSpec, PublicDialog_d_ColorPickerSpec as ColorPickerSpec, PublicDialog_d_CustomEditorSpec as CustomEditorSpec, PublicDialog_d_CustomEditorInit as CustomEditorInit, PublicDialog_d_CustomEditorInitFn as CustomEditorInitFn, PublicDialog_d_DialogData as DialogData, PublicDialog_d_DialogSize as DialogSize, PublicDialog_d_DialogSpec as DialogSpec, PublicDialog_d_DialogInstanceApi as DialogInstanceApi, PublicDialog_d_DialogFooterButtonSpec as DialogFooterButtonSpec, PublicDialog_d_DialogActionDetails as DialogActionDetails, PublicDialog_d_DialogChangeDetails as DialogChangeDetails, PublicDialog_d_DialogTabChangeDetails as DialogTabChangeDetails, PublicDialog_d_DropZoneSpec as DropZoneSpec, PublicDialog_d_GridSpec as GridSpec, PublicDialog_d_HtmlPanelSpec as HtmlPanelSpec, PublicDialog_d_IframeSpec as IframeSpec, PublicDialog_d_ImagePreviewSpec as ImagePreviewSpec, PublicDialog_d_InputSpec as InputSpec, PublicDialog_d_LabelSpec as LabelSpec, PublicDialog_d_ListBoxSpec as ListBoxSpec, PublicDialog_d_ListBoxItemSpec as ListBoxItemSpec, PublicDialog_d_ListBoxNestedItemSpec as ListBoxNestedItemSpec, PublicDialog_d_ListBoxSingleItemSpec as ListBoxSingleItemSpec, PublicDialog_d_PanelSpec as PanelSpec, PublicDialog_d_SelectBoxSpec as SelectBoxSpec, PublicDialog_d_SelectBoxItemSpec as SelectBoxItemSpec, PublicDialog_d_SizeInputSpec as SizeInputSpec, PublicDialog_d_SliderSpec as SliderSpec, PublicDialog_d_TableSpec as TableSpec, PublicDialog_d_TabSpec as TabSpec, PublicDialog_d_TabPanelSpec as TabPanelSpec, PublicDialog_d_TextAreaSpec as TextAreaSpec, PublicDialog_d_TreeSpec as TreeSpec, PublicDialog_d_TreeItemSpec as TreeItemSpec, DirectorySpec as TreeDirectorySpec, LeafSpec as TreeLeafSpec, PublicDialog_d_UrlInputData as UrlInputData, PublicDialog_d_UrlInputSpec as UrlInputSpec, PublicDialog_d_UrlDialogSpec as UrlDialogSpec, PublicDialog_d_UrlDialogFooterButtonSpec as UrlDialogFooterButtonSpec, PublicDialog_d_UrlDialogInstanceApi as UrlDialogInstanceApi, PublicDialog_d_UrlDialogActionDetails as UrlDialogActionDetails, PublicDialog_d_UrlDialogMessage as UrlDialogMessage, }; } type PublicInlineContent_d_AutocompleterSpec = AutocompleterSpec; type PublicInlineContent_d_AutocompleterItemSpec = AutocompleterItemSpec; type PublicInlineContent_d_AutocompleterContents = AutocompleterContents; type PublicInlineContent_d_AutocompleterInstanceApi = AutocompleterInstanceApi; type PublicInlineContent_d_ContextPosition = ContextPosition; type PublicInlineContent_d_ContextScope = ContextScope; type PublicInlineContent_d_ContextFormSpec = ContextFormSpec; type PublicInlineContent_d_ContextFormInstanceApi = ContextFormInstanceApi; type PublicInlineContent_d_ContextFormButtonSpec = ContextFormButtonSpec; type PublicInlineContent_d_ContextFormButtonInstanceApi = ContextFormButtonInstanceApi; type PublicInlineContent_d_ContextFormToggleButtonSpec = ContextFormToggleButtonSpec; type PublicInlineContent_d_ContextFormToggleButtonInstanceApi = ContextFormToggleButtonInstanceApi; type PublicInlineContent_d_ContextToolbarSpec = ContextToolbarSpec; type PublicInlineContent_d_SeparatorItemSpec = SeparatorItemSpec; declare namespace PublicInlineContent_d { export { PublicInlineContent_d_AutocompleterSpec as AutocompleterSpec, PublicInlineContent_d_AutocompleterItemSpec as AutocompleterItemSpec, PublicInlineContent_d_AutocompleterContents as AutocompleterContents, PublicInlineContent_d_AutocompleterInstanceApi as AutocompleterInstanceApi, PublicInlineContent_d_ContextPosition as ContextPosition, PublicInlineContent_d_ContextScope as ContextScope, PublicInlineContent_d_ContextFormSpec as ContextFormSpec, PublicInlineContent_d_ContextFormInstanceApi as ContextFormInstanceApi, PublicInlineContent_d_ContextFormButtonSpec as ContextFormButtonSpec, PublicInlineContent_d_ContextFormButtonInstanceApi as ContextFormButtonInstanceApi, PublicInlineContent_d_ContextFormToggleButtonSpec as ContextFormToggleButtonSpec, PublicInlineContent_d_ContextFormToggleButtonInstanceApi as ContextFormToggleButtonInstanceApi, PublicInlineContent_d_ContextToolbarSpec as ContextToolbarSpec, PublicInlineContent_d_SeparatorItemSpec as SeparatorItemSpec, }; } type PublicMenu_d_MenuItemSpec = MenuItemSpec; type PublicMenu_d_MenuItemInstanceApi = MenuItemInstanceApi; type PublicMenu_d_NestedMenuItemContents = NestedMenuItemContents; type PublicMenu_d_NestedMenuItemSpec = NestedMenuItemSpec; type PublicMenu_d_NestedMenuItemInstanceApi = NestedMenuItemInstanceApi; type PublicMenu_d_FancyMenuItemSpec = FancyMenuItemSpec; type PublicMenu_d_ColorSwatchMenuItemSpec = ColorSwatchMenuItemSpec; type PublicMenu_d_InsertTableMenuItemSpec = InsertTableMenuItemSpec; type PublicMenu_d_ToggleMenuItemSpec = ToggleMenuItemSpec; type PublicMenu_d_ToggleMenuItemInstanceApi = ToggleMenuItemInstanceApi; type PublicMenu_d_ChoiceMenuItemSpec = ChoiceMenuItemSpec; type PublicMenu_d_ChoiceMenuItemInstanceApi = ChoiceMenuItemInstanceApi; type PublicMenu_d_SeparatorMenuItemSpec = SeparatorMenuItemSpec; type PublicMenu_d_ContextMenuApi = ContextMenuApi; type PublicMenu_d_ContextMenuContents = ContextMenuContents; type PublicMenu_d_ContextMenuItem = ContextMenuItem; type PublicMenu_d_ContextSubMenu = ContextSubMenu; type PublicMenu_d_CardMenuItemSpec = CardMenuItemSpec; type PublicMenu_d_CardMenuItemInstanceApi = CardMenuItemInstanceApi; type PublicMenu_d_CardItemSpec = CardItemSpec; type PublicMenu_d_CardContainerSpec = CardContainerSpec; type PublicMenu_d_CardImageSpec = CardImageSpec; type PublicMenu_d_CardTextSpec = CardTextSpec; declare namespace PublicMenu_d { export { PublicMenu_d_MenuItemSpec as MenuItemSpec, PublicMenu_d_MenuItemInstanceApi as MenuItemInstanceApi, PublicMenu_d_NestedMenuItemContents as NestedMenuItemContents, PublicMenu_d_NestedMenuItemSpec as NestedMenuItemSpec, PublicMenu_d_NestedMenuItemInstanceApi as NestedMenuItemInstanceApi, PublicMenu_d_FancyMenuItemSpec as FancyMenuItemSpec, PublicMenu_d_ColorSwatchMenuItemSpec as ColorSwatchMenuItemSpec, PublicMenu_d_InsertTableMenuItemSpec as InsertTableMenuItemSpec, PublicMenu_d_ToggleMenuItemSpec as ToggleMenuItemSpec, PublicMenu_d_ToggleMenuItemInstanceApi as ToggleMenuItemInstanceApi, PublicMenu_d_ChoiceMenuItemSpec as ChoiceMenuItemSpec, PublicMenu_d_ChoiceMenuItemInstanceApi as ChoiceMenuItemInstanceApi, PublicMenu_d_SeparatorMenuItemSpec as SeparatorMenuItemSpec, PublicMenu_d_ContextMenuApi as ContextMenuApi, PublicMenu_d_ContextMenuContents as ContextMenuContents, PublicMenu_d_ContextMenuItem as ContextMenuItem, PublicMenu_d_ContextSubMenu as ContextSubMenu, PublicMenu_d_CardMenuItemSpec as CardMenuItemSpec, PublicMenu_d_CardMenuItemInstanceApi as CardMenuItemInstanceApi, PublicMenu_d_CardItemSpec as CardItemSpec, PublicMenu_d_CardContainerSpec as CardContainerSpec, PublicMenu_d_CardImageSpec as CardImageSpec, PublicMenu_d_CardTextSpec as CardTextSpec, }; } interface SidebarInstanceApi { element: () => HTMLElement; } interface SidebarSpec { icon?: string; tooltip?: string; onShow?: (api: SidebarInstanceApi) => void; onSetup?: (api: SidebarInstanceApi) => (api: SidebarInstanceApi) => void; onHide?: (api: SidebarInstanceApi) => void; } type PublicSidebar_d_SidebarSpec = SidebarSpec; type PublicSidebar_d_SidebarInstanceApi = SidebarInstanceApi; declare namespace PublicSidebar_d { export { PublicSidebar_d_SidebarSpec as SidebarSpec, PublicSidebar_d_SidebarInstanceApi as SidebarInstanceApi, }; } type PublicToolbar_d_ToolbarButtonSpec = ToolbarButtonSpec; type PublicToolbar_d_ToolbarButtonInstanceApi = ToolbarButtonInstanceApi; type PublicToolbar_d_ToolbarSplitButtonSpec = ToolbarSplitButtonSpec; type PublicToolbar_d_ToolbarSplitButtonInstanceApi = ToolbarSplitButtonInstanceApi; type PublicToolbar_d_ToolbarMenuButtonSpec = ToolbarMenuButtonSpec; type PublicToolbar_d_ToolbarMenuButtonInstanceApi = ToolbarMenuButtonInstanceApi; type PublicToolbar_d_ToolbarToggleButtonSpec = ToolbarToggleButtonSpec; type PublicToolbar_d_ToolbarToggleButtonInstanceApi = ToolbarToggleButtonInstanceApi; type PublicToolbar_d_GroupToolbarButtonSpec = GroupToolbarButtonSpec; type PublicToolbar_d_GroupToolbarButtonInstanceApi = GroupToolbarButtonInstanceApi; declare namespace PublicToolbar_d { export { PublicToolbar_d_ToolbarButtonSpec as ToolbarButtonSpec, PublicToolbar_d_ToolbarButtonInstanceApi as ToolbarButtonInstanceApi, PublicToolbar_d_ToolbarSplitButtonSpec as ToolbarSplitButtonSpec, PublicToolbar_d_ToolbarSplitButtonInstanceApi as ToolbarSplitButtonInstanceApi, PublicToolbar_d_ToolbarMenuButtonSpec as ToolbarMenuButtonSpec, PublicToolbar_d_ToolbarMenuButtonInstanceApi as ToolbarMenuButtonInstanceApi, PublicToolbar_d_ToolbarToggleButtonSpec as ToolbarToggleButtonSpec, PublicToolbar_d_ToolbarToggleButtonInstanceApi as ToolbarToggleButtonInstanceApi, PublicToolbar_d_GroupToolbarButtonSpec as GroupToolbarButtonSpec, PublicToolbar_d_GroupToolbarButtonInstanceApi as GroupToolbarButtonInstanceApi, }; } interface ViewButtonApi { setIcon: (newIcon: string) => void; } interface ViewToggleButtonApi extends ViewButtonApi { isActive: () => boolean; setActive: (state: boolean) => void; } interface BaseButtonSpec { text?: string; icon?: string; tooltip?: string; buttonType?: 'primary' | 'secondary'; borderless?: boolean; onAction: (api: Api) => void; } interface ViewNormalButtonSpec extends BaseButtonSpec { text: string; type: 'button'; } interface ViewToggleButtonSpec extends BaseButtonSpec { type: 'togglebutton'; active?: boolean; onAction: (api: ViewToggleButtonApi) => void; } interface ViewButtonsGroupSpec { type: 'group'; buttons: Array; } type ViewButtonSpec = ViewNormalButtonSpec | ViewToggleButtonSpec | ViewButtonsGroupSpec; interface ViewInstanceApi { getContainer: () => HTMLElement; } interface ViewSpec { buttons?: ViewButtonSpec[]; onShow: (api: ViewInstanceApi) => void; onHide: (api: ViewInstanceApi) => void; } type PublicView_d_ViewSpec = ViewSpec; type PublicView_d_ViewInstanceApi = ViewInstanceApi; declare namespace PublicView_d { export { PublicView_d_ViewSpec as ViewSpec, PublicView_d_ViewInstanceApi as ViewInstanceApi, }; } interface Registry$1 { addButton: (name: string, spec: ToolbarButtonSpec) => void; addGroupToolbarButton: (name: string, spec: GroupToolbarButtonSpec) => void; addToggleButton: (name: string, spec: ToolbarToggleButtonSpec) => void; addMenuButton: (name: string, spec: ToolbarMenuButtonSpec) => void; addSplitButton: (name: string, spec: ToolbarSplitButtonSpec) => void; addMenuItem: (name: string, spec: MenuItemSpec) => void; addNestedMenuItem: (name: string, spec: NestedMenuItemSpec) => void; addToggleMenuItem: (name: string, spec: ToggleMenuItemSpec) => void; addContextMenu: (name: string, spec: ContextMenuApi) => void; addContextToolbar: (name: string, spec: ContextToolbarSpec) => void; addContextForm: (name: string, spec: ContextFormSpec) => void; addIcon: (name: string, svgData: string) => void; addAutocompleter: (name: string, spec: AutocompleterSpec) => void; addSidebar: (name: string, spec: SidebarSpec) => void; addView: (name: string, spec: ViewSpec) => void; getAll: () => { buttons: Record; menuItems: Record; popups: Record; contextMenus: Record; contextToolbars: Record; icons: Record; sidebars: Record; views: Record; }; } interface AutocompleteLookupData { readonly matchText: string; readonly items: AutocompleterContents[]; readonly columns: ColumnTypes; readonly onAction: (autoApi: AutocompleterInstanceApi, rng: Range, value: string, meta: Record) => void; readonly highlightOn: string[]; } interface AutocompleterEventArgs { readonly lookupData: AutocompleteLookupData[]; } interface RangeLikeObject { startContainer: Node; startOffset: number; endContainer: Node; endOffset: number; } type ApplyFormat = BlockFormat | InlineFormat | SelectorFormat; type RemoveFormat = RemoveBlockFormat | RemoveInlineFormat | RemoveSelectorFormat; type Format = ApplyFormat | RemoveFormat; type Formats = Record; type FormatAttrOrStyleValue = string | ((vars?: FormatVars) => string | null); type FormatVars = Record; interface BaseFormat { ceFalseOverride?: boolean; classes?: string | string[]; collapsed?: boolean; exact?: boolean; expand?: boolean; links?: boolean; mixed?: boolean; block_expand?: boolean; onmatch?: (node: Element, fmt: T, itemName: string) => boolean; remove?: 'none' | 'empty' | 'all'; remove_similar?: boolean; split?: boolean; deep?: boolean; preserve_attributes?: string[]; } interface Block { block: string; list_block?: string; wrapper?: boolean; } interface Inline { inline: string; } interface Selector { selector: string; inherit?: boolean; } interface CommonFormat extends BaseFormat { attributes?: Record; styles?: Record; toggle?: boolean; preview?: string | false; onformat?: (elm: Element, fmt: T, vars?: FormatVars, node?: Node | RangeLikeObject | null) => void; clear_child_styles?: boolean; merge_siblings?: boolean; merge_with_parents?: boolean; } interface BlockFormat extends Block, CommonFormat { } interface InlineFormat extends Inline, CommonFormat { } interface SelectorFormat extends Selector, CommonFormat { } interface CommonRemoveFormat extends BaseFormat { attributes?: string[] | Record; styles?: string[] | Record; } interface RemoveBlockFormat extends Block, CommonRemoveFormat { } interface RemoveInlineFormat extends Inline, CommonRemoveFormat { } interface RemoveSelectorFormat extends Selector, CommonRemoveFormat { } interface Filter { name: string; callbacks: C[]; } interface ParserArgs { getInner?: boolean | number; forced_root_block?: boolean | string; context?: string; isRootContent?: boolean; format?: string; invalid?: boolean; no_events?: boolean; [key: string]: any; } type ParserFilterCallback = (nodes: AstNode[], name: string, args: ParserArgs) => void; interface ParserFilter extends Filter { } interface DomParserSettings { allow_html_data_urls?: boolean; allow_svg_data_urls?: boolean; allow_conditional_comments?: boolean; allow_html_in_named_anchor?: boolean; allow_script_urls?: boolean; allow_unsafe_link_target?: boolean; blob_cache?: BlobCache; convert_fonts_to_spans?: boolean; convert_unsafe_embeds?: boolean; document?: Document; fix_list_elements?: boolean; font_size_legacy_values?: string; forced_root_block?: boolean | string; forced_root_block_attrs?: Record; inline_styles?: boolean; pad_empty_with_br?: boolean; preserve_cdata?: boolean; root_name?: string; sandbox_iframes?: boolean; sandbox_iframes_exclusions?: string[]; sanitize?: boolean; validate?: boolean; } interface DomParser { schema: Schema; addAttributeFilter: (name: string, callback: ParserFilterCallback) => void; getAttributeFilters: () => ParserFilter[]; removeAttributeFilter: (name: string, callback?: ParserFilterCallback) => void; addNodeFilter: (name: string, callback: ParserFilterCallback) => void; getNodeFilters: () => ParserFilter[]; removeNodeFilter: (name: string, callback?: ParserFilterCallback) => void; parse: (html: string, args?: ParserArgs) => AstNode; } interface StyleSheetLoaderSettings { maxLoadTime?: number; contentCssCors?: boolean; referrerPolicy?: ReferrerPolicy; } interface StyleSheetLoader { load: (url: string) => Promise; loadRawCss: (key: string, css: string) => void; loadAll: (urls: string[]) => Promise; unload: (url: string) => void; unloadRawCss: (key: string) => void; unloadAll: (urls: string[]) => void; _setReferrerPolicy: (referrerPolicy: ReferrerPolicy) => void; _setContentCssCors: (contentCssCors: boolean) => void; } type Registry = Registry$1; interface EditorUiApi { show: () => void; hide: () => void; setEnabled: (state: boolean) => void; isEnabled: () => boolean; } interface EditorUi extends EditorUiApi { registry: Registry; styleSheetLoader: StyleSheetLoader; } type Ui_d_Registry = Registry; type Ui_d_EditorUiApi = EditorUiApi; type Ui_d_EditorUi = EditorUi; declare namespace Ui_d { export { Ui_d_Registry as Registry, PublicDialog_d as Dialog, PublicInlineContent_d as InlineContent, PublicMenu_d as Menu, PublicView_d as View, PublicSidebar_d as Sidebar, PublicToolbar_d as Toolbar, Ui_d_EditorUiApi as EditorUiApi, Ui_d_EditorUi as EditorUi, }; } interface WindowParams { readonly inline?: 'cursor' | 'toolbar' | 'bottom'; readonly ariaAttrs?: boolean; readonly persistent?: boolean; } type InstanceApi = UrlDialogInstanceApi | DialogInstanceApi; interface WindowManagerImpl { open: (config: DialogSpec, params: WindowParams | undefined, closeWindow: (dialog: DialogInstanceApi) => void) => DialogInstanceApi; openUrl: (config: UrlDialogSpec, closeWindow: (dialog: UrlDialogInstanceApi) => void) => UrlDialogInstanceApi; alert: (message: string, callback: () => void) => void; confirm: (message: string, callback: (state: boolean) => void) => void; close: (dialog: InstanceApi) => void; } interface WindowManager { open: (config: DialogSpec, params?: WindowParams) => DialogInstanceApi; openUrl: (config: UrlDialogSpec) => UrlDialogInstanceApi; alert: (message: string, callback?: () => void, scope?: any) => void; confirm: (message: string, callback?: (state: boolean) => void, scope?: any) => void; close: () => void; } interface ExecCommandEvent { command: string; ui: boolean; value?: any; } interface BeforeGetContentEvent extends GetContentArgs { selection?: boolean; } interface GetContentEvent extends BeforeGetContentEvent { content: string; } interface BeforeSetContentEvent extends SetContentArgs { content: string; selection?: boolean; } interface SetContentEvent extends BeforeSetContentEvent { content: string; } interface SaveContentEvent extends GetContentEvent { save: boolean; } interface NewBlockEvent { newBlock: Element; } interface NodeChangeEvent { element: Element; parents: Node[]; selectionChange?: boolean; initial?: boolean; } interface FormatEvent { format: string; vars?: FormatVars; node?: Node | RangeLikeObject | null; } interface ObjectResizeEvent { target: HTMLElement; width: number; height: number; origin: string; } interface ObjectSelectedEvent { target: Node; targetClone?: Node; } interface ScrollIntoViewEvent { elm: HTMLElement; alignToTop: boolean | undefined; } interface SetSelectionRangeEvent { range: Range; forward: boolean | undefined; } interface ShowCaretEvent { target: Node; direction: number; before: boolean; } interface SwitchModeEvent { mode: string; } interface ChangeEvent { level: UndoLevel; lastLevel: UndoLevel | undefined; } interface AddUndoEvent extends ChangeEvent { originalEvent: Event | undefined; } interface UndoRedoEvent { level: UndoLevel; } interface WindowEvent { dialog: InstanceApi; } interface ProgressStateEvent { state: boolean; time?: number; } interface AfterProgressStateEvent { state: boolean; } interface PlaceholderToggleEvent { state: boolean; } interface LoadErrorEvent { message: string; } interface PreProcessEvent extends ParserArgs { node: Element; } interface PostProcessEvent extends ParserArgs { content: string; } interface PastePlainTextToggleEvent { state: boolean; } interface PastePreProcessEvent { content: string; readonly internal: boolean; } interface PastePostProcessEvent { node: HTMLElement; readonly internal: boolean; } interface EditableRootStateChangeEvent { state: boolean; } interface NewTableRowEvent { node: HTMLTableRowElement; } interface NewTableCellEvent { node: HTMLTableCellElement; } interface TableEventData { readonly structure: boolean; readonly style: boolean; } interface TableModifiedEvent extends TableEventData { readonly table: HTMLTableElement; } interface BeforeOpenNotificationEvent { notification: NotificationSpec; } interface OpenNotificationEvent { notification: NotificationApi; } interface EditorEventMap extends Omit { 'activate': { relatedTarget: Editor | null; }; 'deactivate': { relatedTarget: Editor; }; 'focus': { blurredEditor: Editor | null; }; 'blur': { focusedEditor: Editor | null; }; 'resize': UIEvent; 'scroll': UIEvent; 'input': InputEvent; 'beforeinput': InputEvent; 'detach': {}; 'remove': {}; 'init': {}; 'ScrollIntoView': ScrollIntoViewEvent; 'AfterScrollIntoView': ScrollIntoViewEvent; 'ObjectResized': ObjectResizeEvent; 'ObjectResizeStart': ObjectResizeEvent; 'SwitchMode': SwitchModeEvent; 'ScrollWindow': Event; 'ResizeWindow': UIEvent; 'SkinLoaded': {}; 'SkinLoadError': LoadErrorEvent; 'PluginLoadError': LoadErrorEvent; 'ModelLoadError': LoadErrorEvent; 'IconsLoadError': LoadErrorEvent; 'ThemeLoadError': LoadErrorEvent; 'LanguageLoadError': LoadErrorEvent; 'BeforeExecCommand': ExecCommandEvent; 'ExecCommand': ExecCommandEvent; 'NodeChange': NodeChangeEvent; 'FormatApply': FormatEvent; 'FormatRemove': FormatEvent; 'ShowCaret': ShowCaretEvent; 'SelectionChange': {}; 'ObjectSelected': ObjectSelectedEvent; 'BeforeObjectSelected': ObjectSelectedEvent; 'GetSelectionRange': { range: Range; }; 'SetSelectionRange': SetSelectionRangeEvent; 'AfterSetSelectionRange': SetSelectionRangeEvent; 'BeforeGetContent': BeforeGetContentEvent; 'GetContent': GetContentEvent; 'BeforeSetContent': BeforeSetContentEvent; 'SetContent': SetContentEvent; 'SaveContent': SaveContentEvent; 'RawSaveContent': SaveContentEvent; 'LoadContent': { load: boolean; element: HTMLElement; }; 'PreviewFormats': {}; 'AfterPreviewFormats': {}; 'ScriptsLoaded': {}; 'PreInit': {}; 'PostRender': {}; 'NewBlock': NewBlockEvent; 'ClearUndos': {}; 'TypingUndo': {}; 'Redo': UndoRedoEvent; 'Undo': UndoRedoEvent; 'BeforeAddUndo': AddUndoEvent; 'AddUndo': AddUndoEvent; 'change': ChangeEvent; 'CloseWindow': WindowEvent; 'OpenWindow': WindowEvent; 'ProgressState': ProgressStateEvent; 'AfterProgressState': AfterProgressStateEvent; 'PlaceholderToggle': PlaceholderToggleEvent; 'tap': TouchEvent; 'longpress': TouchEvent; 'longpresscancel': {}; 'PreProcess': PreProcessEvent; 'PostProcess': PostProcessEvent; 'AutocompleterStart': AutocompleterEventArgs; 'AutocompleterUpdate': AutocompleterEventArgs; 'AutocompleterEnd': {}; 'PastePlainTextToggle': PastePlainTextToggleEvent; 'PastePreProcess': PastePreProcessEvent; 'PastePostProcess': PastePostProcessEvent; 'TableModified': TableModifiedEvent; 'NewRow': NewTableRowEvent; 'NewCell': NewTableCellEvent; 'SetAttrib': SetAttribEvent; 'hide': {}; 'show': {}; 'dirty': {}; 'BeforeOpenNotification': BeforeOpenNotificationEvent; 'OpenNotification': OpenNotificationEvent; } interface EditorManagerEventMap { 'AddEditor': { editor: Editor; }; 'RemoveEditor': { editor: Editor; }; 'BeforeUnload': { returnValue: any; }; } type EventTypes_d_ExecCommandEvent = ExecCommandEvent; type EventTypes_d_BeforeGetContentEvent = BeforeGetContentEvent; type EventTypes_d_GetContentEvent = GetContentEvent; type EventTypes_d_BeforeSetContentEvent = BeforeSetContentEvent; type EventTypes_d_SetContentEvent = SetContentEvent; type EventTypes_d_SaveContentEvent = SaveContentEvent; type EventTypes_d_NewBlockEvent = NewBlockEvent; type EventTypes_d_NodeChangeEvent = NodeChangeEvent; type EventTypes_d_FormatEvent = FormatEvent; type EventTypes_d_ObjectResizeEvent = ObjectResizeEvent; type EventTypes_d_ObjectSelectedEvent = ObjectSelectedEvent; type EventTypes_d_ScrollIntoViewEvent = ScrollIntoViewEvent; type EventTypes_d_SetSelectionRangeEvent = SetSelectionRangeEvent; type EventTypes_d_ShowCaretEvent = ShowCaretEvent; type EventTypes_d_SwitchModeEvent = SwitchModeEvent; type EventTypes_d_ChangeEvent = ChangeEvent; type EventTypes_d_AddUndoEvent = AddUndoEvent; type EventTypes_d_UndoRedoEvent = UndoRedoEvent; type EventTypes_d_WindowEvent = WindowEvent; type EventTypes_d_ProgressStateEvent = ProgressStateEvent; type EventTypes_d_AfterProgressStateEvent = AfterProgressStateEvent; type EventTypes_d_PlaceholderToggleEvent = PlaceholderToggleEvent; type EventTypes_d_LoadErrorEvent = LoadErrorEvent; type EventTypes_d_PreProcessEvent = PreProcessEvent; type EventTypes_d_PostProcessEvent = PostProcessEvent; type EventTypes_d_PastePlainTextToggleEvent = PastePlainTextToggleEvent; type EventTypes_d_PastePreProcessEvent = PastePreProcessEvent; type EventTypes_d_PastePostProcessEvent = PastePostProcessEvent; type EventTypes_d_EditableRootStateChangeEvent = EditableRootStateChangeEvent; type EventTypes_d_NewTableRowEvent = NewTableRowEvent; type EventTypes_d_NewTableCellEvent = NewTableCellEvent; type EventTypes_d_TableEventData = TableEventData; type EventTypes_d_TableModifiedEvent = TableModifiedEvent; type EventTypes_d_BeforeOpenNotificationEvent = BeforeOpenNotificationEvent; type EventTypes_d_OpenNotificationEvent = OpenNotificationEvent; type EventTypes_d_EditorEventMap = EditorEventMap; type EventTypes_d_EditorManagerEventMap = EditorManagerEventMap; declare namespace EventTypes_d { export { EventTypes_d_ExecCommandEvent as ExecCommandEvent, EventTypes_d_BeforeGetContentEvent as BeforeGetContentEvent, EventTypes_d_GetContentEvent as GetContentEvent, EventTypes_d_BeforeSetContentEvent as BeforeSetContentEvent, EventTypes_d_SetContentEvent as SetContentEvent, EventTypes_d_SaveContentEvent as SaveContentEvent, EventTypes_d_NewBlockEvent as NewBlockEvent, EventTypes_d_NodeChangeEvent as NodeChangeEvent, EventTypes_d_FormatEvent as FormatEvent, EventTypes_d_ObjectResizeEvent as ObjectResizeEvent, EventTypes_d_ObjectSelectedEvent as ObjectSelectedEvent, EventTypes_d_ScrollIntoViewEvent as ScrollIntoViewEvent, EventTypes_d_SetSelectionRangeEvent as SetSelectionRangeEvent, EventTypes_d_ShowCaretEvent as ShowCaretEvent, EventTypes_d_SwitchModeEvent as SwitchModeEvent, EventTypes_d_ChangeEvent as ChangeEvent, EventTypes_d_AddUndoEvent as AddUndoEvent, EventTypes_d_UndoRedoEvent as UndoRedoEvent, EventTypes_d_WindowEvent as WindowEvent, EventTypes_d_ProgressStateEvent as ProgressStateEvent, EventTypes_d_AfterProgressStateEvent as AfterProgressStateEvent, EventTypes_d_PlaceholderToggleEvent as PlaceholderToggleEvent, EventTypes_d_LoadErrorEvent as LoadErrorEvent, EventTypes_d_PreProcessEvent as PreProcessEvent, EventTypes_d_PostProcessEvent as PostProcessEvent, EventTypes_d_PastePlainTextToggleEvent as PastePlainTextToggleEvent, EventTypes_d_PastePreProcessEvent as PastePreProcessEvent, EventTypes_d_PastePostProcessEvent as PastePostProcessEvent, EventTypes_d_EditableRootStateChangeEvent as EditableRootStateChangeEvent, EventTypes_d_NewTableRowEvent as NewTableRowEvent, EventTypes_d_NewTableCellEvent as NewTableCellEvent, EventTypes_d_TableEventData as TableEventData, EventTypes_d_TableModifiedEvent as TableModifiedEvent, EventTypes_d_BeforeOpenNotificationEvent as BeforeOpenNotificationEvent, EventTypes_d_OpenNotificationEvent as OpenNotificationEvent, EventTypes_d_EditorEventMap as EditorEventMap, EventTypes_d_EditorManagerEventMap as EditorManagerEventMap, }; } type Format_d_Formats = Formats; type Format_d_Format = Format; type Format_d_ApplyFormat = ApplyFormat; type Format_d_BlockFormat = BlockFormat; type Format_d_InlineFormat = InlineFormat; type Format_d_SelectorFormat = SelectorFormat; type Format_d_RemoveFormat = RemoveFormat; type Format_d_RemoveBlockFormat = RemoveBlockFormat; type Format_d_RemoveInlineFormat = RemoveInlineFormat; type Format_d_RemoveSelectorFormat = RemoveSelectorFormat; declare namespace Format_d { export { Format_d_Formats as Formats, Format_d_Format as Format, Format_d_ApplyFormat as ApplyFormat, Format_d_BlockFormat as BlockFormat, Format_d_InlineFormat as InlineFormat, Format_d_SelectorFormat as SelectorFormat, Format_d_RemoveFormat as RemoveFormat, Format_d_RemoveBlockFormat as RemoveBlockFormat, Format_d_RemoveInlineFormat as RemoveInlineFormat, Format_d_RemoveSelectorFormat as RemoveSelectorFormat, }; } type StyleFormat = BlockStyleFormat | InlineStyleFormat | SelectorStyleFormat; type AllowedFormat = Separator | FormatReference | StyleFormat | NestedFormatting; interface Separator { title: string; } interface FormatReference { title: string; format: string; icon?: string; } interface NestedFormatting { title: string; items: Array; } interface CommonStyleFormat { name?: string; title: string; icon?: string; } interface BlockStyleFormat extends BlockFormat, CommonStyleFormat { } interface InlineStyleFormat extends InlineFormat, CommonStyleFormat { } interface SelectorStyleFormat extends SelectorFormat, CommonStyleFormat { } type EntityEncoding = 'named' | 'numeric' | 'raw' | 'named,numeric' | 'named+numeric' | 'numeric,named' | 'numeric+named'; interface ContentLanguage { readonly title: string; readonly code: string; readonly customCode?: string; } type ThemeInitFunc = (editor: Editor, elm: HTMLElement) => { editorContainer: HTMLElement; iframeContainer: HTMLElement; height?: number; iframeHeight?: number; api?: EditorUiApi; }; type SetupCallback = (editor: Editor) => void; type FilePickerCallback = (callback: (value: string, meta?: Record) => void, value: string, meta: Record) => void; type FilePickerValidationStatus = 'valid' | 'unknown' | 'invalid' | 'none'; type FilePickerValidationCallback = (info: { type: string; url: string; }, callback: (validation: { status: FilePickerValidationStatus; message: string; }) => void) => void; type PastePreProcessFn = (editor: Editor, args: PastePreProcessEvent) => void; type PastePostProcessFn = (editor: Editor, args: PastePostProcessEvent) => void; type URLConverter = (url: string, name: string, elm?: string | Element) => string; type URLConverterCallback = (url: string, node: Node | string | undefined, on_save: boolean, name: string) => string; interface ToolbarGroup { name?: string; items: string[]; } type ToolbarMode = 'floating' | 'sliding' | 'scrolling' | 'wrap'; type ToolbarLocation = 'top' | 'bottom' | 'auto'; interface BaseEditorOptions { a11y_advanced_options?: boolean; add_form_submit_trigger?: boolean; add_unload_trigger?: boolean; allow_conditional_comments?: boolean; allow_html_data_urls?: boolean; allow_html_in_named_anchor?: boolean; allow_script_urls?: boolean; allow_svg_data_urls?: boolean; allow_unsafe_link_target?: boolean; anchor_bottom?: false | string; anchor_top?: false | string; auto_focus?: string | true; automatic_uploads?: boolean; base_url?: string; block_formats?: string; block_unsupported_drop?: boolean; body_id?: string; body_class?: string; br_in_pre?: boolean; br_newline_selector?: string; browser_spellcheck?: boolean; branding?: boolean; cache_suffix?: string; color_cols?: number; color_cols_foreground?: number; color_cols_background?: number; color_map?: string[]; color_map_foreground?: string[]; color_map_background?: string[]; color_default_foreground?: string; color_default_background?: string; content_css?: boolean | string | string[]; content_css_cors?: boolean; content_security_policy?: string; content_style?: string; content_langs?: ContentLanguage[]; contextmenu?: string | string[] | false; contextmenu_never_use_native?: boolean; convert_fonts_to_spans?: boolean; convert_unsafe_embeds?: boolean; convert_urls?: boolean; custom_colors?: boolean; custom_elements?: string | Record; custom_ui_selector?: string; custom_undo_redo_levels?: number; default_font_stack?: string[]; deprecation_warnings?: boolean; directionality?: 'ltr' | 'rtl'; doctype?: string; document_base_url?: string; draggable_modal?: boolean; editable_class?: string; editable_root?: boolean; element_format?: 'xhtml' | 'html'; elementpath?: boolean; encoding?: string; end_container_on_empty_block?: boolean | string; entities?: string; entity_encoding?: EntityEncoding; extended_valid_elements?: string; event_root?: string; file_picker_callback?: FilePickerCallback; file_picker_types?: string; file_picker_validator_handler?: FilePickerValidationCallback; fix_list_elements?: boolean; fixed_toolbar_container?: string; fixed_toolbar_container_target?: HTMLElement; font_css?: string | string[]; font_family_formats?: string; font_size_classes?: string; font_size_legacy_values?: string; font_size_style_values?: string; font_size_formats?: string; font_size_input_default_unit?: string; forced_root_block?: string; forced_root_block_attrs?: Record; formats?: Formats; format_noneditable_selector?: string; height?: number | string; help_accessibility?: boolean; hidden_input?: boolean; highlight_on_focus?: boolean; icons?: string; icons_url?: string; id?: string; iframe_aria_text?: string; iframe_attrs?: Record; images_file_types?: string; images_replace_blob_uris?: boolean; images_reuse_filename?: boolean; images_upload_base_path?: string; images_upload_credentials?: boolean; images_upload_handler?: UploadHandler; images_upload_url?: string; indent?: boolean; indent_after?: string; indent_before?: string; indent_use_margin?: boolean; indentation?: string; init_instance_callback?: SetupCallback; inline?: boolean; inline_boundaries?: boolean; inline_boundaries_selector?: string; inline_styles?: boolean; invalid_elements?: string; invalid_styles?: string | Record; keep_styles?: boolean; language?: string; language_load?: boolean; language_url?: string; line_height_formats?: string; max_height?: number; max_width?: number; menu?: Record; menubar?: boolean | string; min_height?: number; min_width?: number; model?: string; model_url?: string; newdocument_content?: string; newline_behavior?: 'block' | 'linebreak' | 'invert' | 'default'; no_newline_selector?: string; noneditable_class?: string; noneditable_regexp?: RegExp | RegExp[]; nowrap?: boolean; object_resizing?: boolean | string; pad_empty_with_br?: boolean; paste_as_text?: boolean; paste_block_drop?: boolean; paste_data_images?: boolean; paste_merge_formats?: boolean; paste_postprocess?: PastePostProcessFn; paste_preprocess?: PastePreProcessFn; paste_remove_styles_if_webkit?: boolean; paste_tab_spaces?: number; paste_webkit_styles?: string; placeholder?: string; preserve_cdata?: boolean; preview_styles?: false | string; promotion?: boolean; protect?: RegExp[]; readonly?: boolean; referrer_policy?: ReferrerPolicy; relative_urls?: boolean; remove_script_host?: boolean; remove_trailing_brs?: boolean; removed_menuitems?: string; resize?: boolean | 'both'; resize_img_proportional?: boolean; root_name?: string; sandbox_iframes?: boolean; sandbox_iframes_exclusions?: string[]; schema?: SchemaType; selector?: string; setup?: SetupCallback; sidebar_show?: string; skin?: boolean | string; skin_url?: string; smart_paste?: boolean; statusbar?: boolean; style_formats?: AllowedFormat[]; style_formats_autohide?: boolean; style_formats_merge?: boolean; submit_patch?: boolean; suffix?: string; table_tab_navigation?: boolean; target?: HTMLElement; text_patterns?: RawPattern[] | false; text_patterns_lookup?: RawDynamicPatternsLookup; theme?: string | ThemeInitFunc | false; theme_url?: string; toolbar?: boolean | string | string[] | Array; toolbar1?: string; toolbar2?: string; toolbar3?: string; toolbar4?: string; toolbar5?: string; toolbar6?: string; toolbar7?: string; toolbar8?: string; toolbar9?: string; toolbar_groups?: Record; toolbar_location?: ToolbarLocation; toolbar_mode?: ToolbarMode; toolbar_sticky?: boolean; toolbar_sticky_offset?: number; typeahead_urls?: boolean; ui_mode?: 'combined' | 'split'; url_converter?: URLConverter; url_converter_scope?: any; urlconverter_callback?: URLConverterCallback; valid_children?: string; valid_classes?: string | Record; valid_elements?: string; valid_styles?: string | Record; verify_html?: boolean; visual?: boolean; visual_anchor_class?: string; visual_table_class?: string; width?: number | string; xss_sanitization?: boolean; license_key?: string; disable_nodechange?: boolean; forced_plugins?: string | string[]; plugin_base_urls?: Record; service_message?: string; [key: string]: any; } interface RawEditorOptions extends BaseEditorOptions { external_plugins?: Record; mobile?: RawEditorOptions; plugins?: string | string[]; } interface NormalizedEditorOptions extends BaseEditorOptions { external_plugins: Record; forced_plugins: string[]; plugins: string[]; } interface EditorOptions extends NormalizedEditorOptions { a11y_advanced_options: boolean; allow_unsafe_link_target: boolean; anchor_bottom: string; anchor_top: string; automatic_uploads: boolean; block_formats: string; body_class: string; body_id: string; br_newline_selector: string; color_map: string[]; color_cols: number; color_cols_foreground: number; color_cols_background: number; color_default_background: string; color_default_foreground: string; content_css: string[]; contextmenu: string[]; convert_unsafe_embeds: boolean; custom_colors: boolean; default_font_stack: string[]; document_base_url: string; init_content_sync: boolean; draggable_modal: boolean; editable_class: string; editable_root: boolean; font_css: string[]; font_family_formats: string; font_size_classes: string; font_size_formats: string; font_size_input_default_unit: string; font_size_legacy_values: string; font_size_style_values: string; forced_root_block: string; forced_root_block_attrs: Record; format_noneditable_selector: string; height: number | string; highlight_on_focus: boolean; iframe_attrs: Record; images_file_types: string; images_upload_base_path: string; images_upload_credentials: boolean; images_upload_url: string; indent_use_margin: boolean; indentation: string; inline: boolean; inline_boundaries_selector: string; language: string; language_load: boolean; language_url: string; line_height_formats: string; menu: Record; menubar: boolean | string; model: string; newdocument_content: string; no_newline_selector: string; noneditable_class: string; noneditable_regexp: RegExp[]; object_resizing: string; pad_empty_with_br: boolean; paste_as_text: boolean; preview_styles: string; promotion: boolean; readonly: boolean; removed_menuitems: string; sandbox_iframes: boolean; sandbox_iframes_exclusions: string[]; toolbar: boolean | string | string[] | Array; toolbar_groups: Record; toolbar_location: ToolbarLocation; toolbar_mode: ToolbarMode; toolbar_persist: boolean; toolbar_sticky: boolean; toolbar_sticky_offset: number; text_patterns: Pattern[]; text_patterns_lookup: DynamicPatternsLookup; visual: boolean; visual_anchor_class: string; visual_table_class: string; width: number | string; xss_sanitization: boolean; } type StyleMap = Record; interface StylesSettings { allow_script_urls?: boolean; allow_svg_data_urls?: boolean; url_converter?: URLConverter; url_converter_scope?: any; } interface Styles { parse: (css: string | undefined) => Record; serialize: (styles: StyleMap, elementName?: string) => string; } type EventUtilsCallback = (event: EventUtilsEvent) => void | boolean; type EventUtilsEvent = NormalizedEvent & { metaKey: boolean; }; interface Callback$1 { func: EventUtilsCallback; scope: any; } interface CallbackList extends Array> { fakeName: string | false; capture: boolean; nativeHandler: EventListener; } interface EventUtilsConstructor { readonly prototype: EventUtils; new (): EventUtils; Event: EventUtils; } declare class EventUtils { static Event: EventUtils; domLoaded: boolean; events: Record>>; private readonly expando; private hasFocusIn; private count; constructor(); bind(target: any, name: K, callback: EventUtilsCallback, scope?: any): EventUtilsCallback; bind(target: any, names: string, callback: EventUtilsCallback, scope?: any): EventUtilsCallback; unbind(target: any, name: K, callback?: EventUtilsCallback): this; unbind(target: any, names: string, callback?: EventUtilsCallback): this; unbind(target: any): this; fire(target: any, name: string, args?: {}): this; dispatch(target: any, name: string, args?: {}): this; clean(target: any): this; destroy(): void; cancel(e: EventUtilsEvent): boolean; private executeHandlers; } interface SetAttribEvent { attrElm: HTMLElement; attrName: string; attrValue: string | boolean | number | null; } interface DOMUtilsSettings { schema: Schema; url_converter: URLConverter; url_converter_scope: any; ownEvents: boolean; keep_values: boolean; update_styles: boolean; root_element: HTMLElement | null; collect: boolean; onSetAttrib: (event: SetAttribEvent) => void; contentCssCors: boolean; referrerPolicy: ReferrerPolicy; } type Target = Node | Window; type RunArguments = string | T | Array | null; type BoundEvent = [ Target, string, EventUtilsCallback, any ]; type Callback = EventUtilsCallback>; type RunResult = T extends Array ? R[] : false | R; interface DOMUtils { doc: Document; settings: Partial; win: Window; files: Record; stdMode: boolean; boxModel: boolean; styleSheetLoader: StyleSheetLoader; boundEvents: BoundEvent[]; styles: Styles; schema: Schema; events: EventUtils; root: Node | null; isBlock: { (node: Node | null): node is HTMLElement; (node: string): boolean; }; clone: (node: Node, deep: boolean) => Node; getRoot: () => HTMLElement; getViewPort: (argWin?: Window) => GeomRect; getRect: (elm: string | HTMLElement) => GeomRect; getSize: (elm: string | HTMLElement) => { w: number; h: number; }; getParent: { (node: string | Node | null, selector: K, root?: Node): HTMLElementTagNameMap[K] | null; (node: string | Node | null, selector: string | ((node: Node) => node is T), root?: Node): T | null; (node: string | Node | null, selector?: string | ((node: Node) => boolean | void), root?: Node): Node | null; }; getParents: { (elm: string | HTMLElementTagNameMap[K] | null, selector: K, root?: Node, collect?: boolean): Array; (node: string | Node | null, selector: string | ((node: Node) => node is T), root?: Node, collect?: boolean): T[]; (elm: string | Node | null, selector?: string | ((node: Node) => boolean | void), root?: Node, collect?: boolean): Node[]; }; get: { (elm: T): T; (elm: string): HTMLElement | null; }; getNext: (node: Node | null, selector: string | ((node: Node) => boolean)) => Node | null; getPrev: (node: Node | null, selector: string | ((node: Node) => boolean)) => Node | null; select: { (selector: K, scope?: string | Node): Array; (selector: string, scope?: string | Node): T[]; }; is: { (elm: Node | Node[] | null, selector: string): elm is T; (elm: Node | Node[] | null, selector: string): boolean; }; add: (parentElm: RunArguments, name: string | Element, attrs?: Record, html?: string | Node | null, create?: boolean) => HTMLElement; create: { (name: K, attrs?: Record, html?: string | Node | null): HTMLElementTagNameMap[K]; (name: string, attrs?: Record, html?: string | Node | null): HTMLElement; }; createHTML: (name: string, attrs?: Record, html?: string) => string; createFragment: (html?: string) => DocumentFragment; remove: { (node: T | T[], keepChildren?: boolean): typeof node extends Array ? T[] : T; (node: string, keepChildren?: boolean): T | false; }; getStyle: { (elm: Element, name: string, computed: true): string; (elm: string | Element | null, name: string, computed?: boolean): string | undefined; }; setStyle: (elm: string | Element | Element[], name: string, value: string | number | null) => void; setStyles: (elm: string | Element | Element[], stylesArg: StyleMap) => void; removeAllAttribs: (e: RunArguments) => void; setAttrib: (elm: RunArguments, name: string, value: string | boolean | number | null) => void; setAttribs: (elm: RunArguments, attrs: Record) => void; getAttrib: (elm: string | Element | null, name: string, defaultVal?: string) => string; getAttribs: (elm: string | Element) => NamedNodeMap | Attr[]; getPos: (elm: string | Element, rootElm?: Node) => { x: number; y: number; }; parseStyle: (cssText: string) => Record; serializeStyle: (stylesArg: StyleMap, name?: string) => string; addStyle: (cssText: string) => void; loadCSS: (url: string) => void; hasClass: (elm: string | Element, cls: string) => boolean; addClass: (elm: RunArguments, cls: string) => void; removeClass: (elm: RunArguments, cls: string) => void; toggleClass: (elm: RunArguments, cls: string, state?: boolean) => void; show: (elm: string | Node | Node[]) => void; hide: (elm: string | Node | Node[]) => void; isHidden: (elm: string | Node) => boolean; uniqueId: (prefix?: string) => string; setHTML: (elm: RunArguments, html: string) => void; getOuterHTML: (elm: string | Node) => string; setOuterHTML: (elm: string | Node | Node[], html: string) => void; decode: (text: string) => string; encode: (text: string) => string; insertAfter: { (node: T | T[], reference: string | Node): T; (node: RunArguments, reference: string | Node): RunResult; }; replace: { (newElm: Node, oldElm: T | T[], keepChildren?: boolean): T; (newElm: Node, oldElm: RunArguments, keepChildren?: boolean): false | T; }; rename: { (elm: Element, name: K): HTMLElementTagNameMap[K]; (elm: Element, name: string): Element; }; findCommonAncestor: (a: Node, b: Node) => Node | null; run(this: DOMUtils, elm: T | T[], func: (node: T) => R, scope?: any): typeof elm extends Array ? R[] : R; run(this: DOMUtils, elm: RunArguments, func: (node: T) => R, scope?: any): RunResult; isEmpty: (node: Node, elements?: Record, options?: IsEmptyOptions) => boolean; createRng: () => Range; nodeIndex: (node: Node, normalized?: boolean) => number; split: { (parentElm: Node, splitElm: Node, replacementElm: T): T | undefined; (parentElm: Node, splitElm: T): T | undefined; }; bind: { (target: Target, name: K, func: Callback, scope?: any): Callback; (target: Target[], name: K, func: Callback, scope?: any): Callback[]; }; unbind: { (target: Target, name?: K, func?: EventUtilsCallback>): EventUtils; (target: Target[], name?: K, func?: EventUtilsCallback>): EventUtils[]; }; fire: (target: Node | Window, name: string, evt?: {}) => EventUtils; dispatch: (target: Node | Window, name: string, evt?: {}) => EventUtils; getContentEditable: (node: Node) => string | null; getContentEditableParent: (node: Node) => string | null; isEditable: (node: Node | null | undefined) => boolean; destroy: () => void; isChildOf: (node: Node, parent: Node) => boolean; dumpRng: (r: Range) => string; } interface ClientRect { left: number; top: number; bottom: number; right: number; width: number; height: number; } interface BookmarkManager { getBookmark: (type?: number, normalized?: boolean) => Bookmark; moveToBookmark: (bookmark: Bookmark) => void; } interface ControlSelection { isResizable: (elm: Element) => boolean; showResizeRect: (elm: HTMLElement) => void; hideResizeRect: () => void; updateResizeRect: (evt: EditorEvent) => void; destroy: () => void; } interface WriterSettings { element_format?: 'xhtml' | 'html'; entities?: string; entity_encoding?: EntityEncoding; indent?: boolean; indent_after?: string; indent_before?: string; } type Attributes = Array<{ name: string; value: string; }>; interface Writer { cdata: (text: string) => void; comment: (text: string) => void; doctype: (text: string) => void; end: (name: string) => void; getContent: () => string; pi: (name: string, text?: string) => void; reset: () => void; start: (name: string, attrs?: Attributes | null, empty?: boolean) => void; text: (text: string, raw?: boolean) => void; } interface HtmlSerializerSettings extends WriterSettings { inner?: boolean; validate?: boolean; } interface HtmlSerializer { serialize: (node: AstNode) => string; } interface DomSerializerSettings extends DomParserSettings, WriterSettings, SchemaSettings, HtmlSerializerSettings { remove_trailing_brs?: boolean; url_converter?: URLConverter; url_converter_scope?: {}; } interface DomSerializerImpl { schema: Schema; addNodeFilter: (name: string, callback: ParserFilterCallback) => void; addAttributeFilter: (name: string, callback: ParserFilterCallback) => void; getNodeFilters: () => ParserFilter[]; getAttributeFilters: () => ParserFilter[]; removeNodeFilter: (name: string, callback?: ParserFilterCallback) => void; removeAttributeFilter: (name: string, callback?: ParserFilterCallback) => void; serialize: { (node: Element, parserArgs: { format: 'tree'; } & ParserArgs): AstNode; (node: Element, parserArgs?: ParserArgs): string; }; addRules: (rules: string) => void; setRules: (rules: string) => void; addTempAttr: (name: string) => void; getTempAttrs: () => string[]; } interface DomSerializer extends DomSerializerImpl { } interface EditorSelection { bookmarkManager: BookmarkManager; controlSelection: ControlSelection; dom: DOMUtils; win: Window; serializer: DomSerializer; editor: Editor; collapse: (toStart?: boolean) => void; setCursorLocation: { (node: Node, offset: number): void; (): void; }; getContent: { (args: { format: 'tree'; } & Partial): AstNode; (args?: Partial): string; }; setContent: (content: string, args?: Partial) => void; getBookmark: (type?: number, normalized?: boolean) => Bookmark; moveToBookmark: (bookmark: Bookmark) => void; select: (node: Node, content?: boolean) => Node; isCollapsed: () => boolean; isEditable: () => boolean; isForward: () => boolean; setNode: (elm: Element) => Element; getNode: () => HTMLElement; getSel: () => Selection | null; setRng: (rng: Range, forward?: boolean) => void; getRng: () => Range; getStart: (real?: boolean) => Element; getEnd: (real?: boolean) => Element; getSelectedBlocks: (startElm?: Element, endElm?: Element) => Element[]; normalize: () => Range; selectorChanged: (selector: string, callback: (active: boolean, args: { node: Node; selector: String; parents: Node[]; }) => void) => EditorSelection; selectorChangedWithUnbind: (selector: string, callback: (active: boolean, args: { node: Node; selector: String; parents: Node[]; }) => void) => { unbind: () => void; }; getScrollContainer: () => HTMLElement | undefined; scrollIntoView: (elm?: HTMLElement, alignToTop?: boolean) => void; placeCaretAt: (clientX: number, clientY: number) => void; getBoundingClientRect: () => ClientRect | DOMRect; destroy: () => void; expand: (options?: { type: 'word'; }) => void; } type EditorCommandCallback = (this: S, ui: boolean, value: any) => void; type EditorCommandsCallback = (command: string, ui: boolean, value?: any) => void; interface Commands { state: Record boolean>; exec: Record; value: Record string>; } interface ExecCommandArgs { skip_focus?: boolean; } interface EditorCommandsConstructor { readonly prototype: EditorCommands; new (editor: Editor): EditorCommands; } declare class EditorCommands { private readonly editor; private commands; constructor(editor: Editor); execCommand(command: string, ui?: boolean, value?: any, args?: ExecCommandArgs): boolean; queryCommandState(command: string): boolean; queryCommandValue(command: string): string; addCommands(commandList: Commands[K], type: K): void; addCommands(commandList: Record): void; addCommand(command: string, callback: EditorCommandCallback, scope: S): void; addCommand(command: string, callback: EditorCommandCallback): void; queryCommandSupported(command: string): boolean; addQueryStateHandler(command: string, callback: (this: S) => boolean, scope: S): void; addQueryStateHandler(command: string, callback: (this: Editor) => boolean): void; addQueryValueHandler(command: string, callback: (this: S) => string, scope: S): void; addQueryValueHandler(command: string, callback: (this: Editor) => string): void; } interface RawString { raw: string; } type Primitive = string | number | boolean | Record | Function; type TokenisedString = [ string, ...Primitive[] ]; type Untranslated = Primitive | TokenisedString | RawString | null | undefined; type TranslatedString = string; interface I18n { getData: () => Record>; setCode: (newCode: string) => void; getCode: () => string; add: (code: string, items: Record) => void; translate: (text: Untranslated) => TranslatedString; isRtl: () => boolean; hasCode: (code: string) => boolean; } interface Observable { fire>(name: K, args?: U, bubble?: boolean): EditorEvent; dispatch>(name: K, args?: U, bubble?: boolean): EditorEvent; on(name: K, callback: (event: EditorEvent>) => void, prepend?: boolean): EventDispatcher; off(name?: K, callback?: (event: EditorEvent>) => void): EventDispatcher; once(name: K, callback: (event: EditorEvent>) => void): EventDispatcher; hasEventListeners(name: string): boolean; } interface URISettings { base_uri?: URI; } interface URIConstructor { readonly prototype: URI; new (url: string, settings?: URISettings): URI; getDocumentBaseUrl: (loc: { protocol: string; host?: string; href?: string; pathname?: string; }) => string; parseDataUri: (uri: string) => { type: string; data: string; }; } interface SafeUriOptions { readonly allow_html_data_urls?: boolean; readonly allow_script_urls?: boolean; readonly allow_svg_data_urls?: boolean; } declare class URI { static parseDataUri(uri: string): { type: string | undefined; data: string; }; static isDomSafe(uri: string, context?: string, options?: SafeUriOptions): boolean; static getDocumentBaseUrl(loc: { protocol: string; host?: string; href?: string; pathname?: string; }): string; source: string; protocol: string | undefined; authority: string | undefined; userInfo: string | undefined; user: string | undefined; password: string | undefined; host: string | undefined; port: string | undefined; relative: string | undefined; path: string; directory: string; file: string | undefined; query: string | undefined; anchor: string | undefined; settings: URISettings; constructor(url: string, settings?: URISettings); setPath(path: string): void; toRelative(uri: string): string; toAbsolute(uri: string, noHost?: boolean): string; isSameOrigin(uri: URI): boolean; toRelPath(base: string, path: string): string; toAbsPath(base: string, path: string): string; getURI(noProtoHost?: boolean): string; } interface EditorManager extends Observable { defaultOptions: RawEditorOptions; majorVersion: string; minorVersion: string; releaseDate: string; activeEditor: Editor | null; focusedEditor: Editor | null; baseURI: URI; baseURL: string; documentBaseURL: string; i18n: I18n; suffix: string; add(this: EditorManager, editor: Editor): Editor; addI18n: (code: string, item: Record) => void; createEditor(this: EditorManager, id: string, options: RawEditorOptions): Editor; execCommand(this: EditorManager, cmd: string, ui: boolean, value: any): boolean; get(this: EditorManager): Editor[]; get(this: EditorManager, id: number | string): Editor | null; init(this: EditorManager, options: RawEditorOptions): Promise; overrideDefaults(this: EditorManager, defaultOptions: Partial): void; remove(this: EditorManager): void; remove(this: EditorManager, selector: string): void; remove(this: EditorManager, editor: Editor): Editor | null; setActive(this: EditorManager, editor: Editor): void; setup(this: EditorManager): void; translate: (text: Untranslated) => TranslatedString; triggerSave: () => void; _setBaseUrl(this: EditorManager, baseUrl: string): void; } interface EditorObservable extends Observable { bindPendingEventDelegates(this: Editor): void; toggleNativeEvent(this: Editor, name: string, state: boolean): void; unbindAllNativeEvents(this: Editor): void; } interface ProcessorSuccess { valid: true; value: T; } interface ProcessorError { valid: false; message: string; } type SimpleProcessor = (value: unknown) => boolean; type Processor = (value: unknown) => ProcessorSuccess | ProcessorError; interface BuiltInOptionTypeMap { 'string': string; 'number': number; 'boolean': boolean; 'array': any[]; 'function': Function; 'object': any; 'string[]': string[]; 'object[]': any[]; 'regexp': RegExp; } type BuiltInOptionType = keyof BuiltInOptionTypeMap; interface BaseOptionSpec { immutable?: boolean; deprecated?: boolean; docsUrl?: string; } interface BuiltInOptionSpec extends BaseOptionSpec { processor: K; default?: BuiltInOptionTypeMap[K]; } interface SimpleOptionSpec extends BaseOptionSpec { processor: SimpleProcessor; default?: T; } interface OptionSpec extends BaseOptionSpec { processor: Processor; default?: T; } interface Options { register: { (name: string, spec: BuiltInOptionSpec): void; (name: K, spec: OptionSpec | SimpleOptionSpec): void; (name: string, spec: OptionSpec): void; (name: string, spec: SimpleOptionSpec): void; }; isRegistered: (name: string) => boolean; get: { (name: K): EditorOptions[K]; (name: string): T | undefined; }; set: (name: K, value: K extends keyof NormalizedEditorOptions ? NormalizedEditorOptions[K] : T) => boolean; unset: (name: string) => boolean; isSet: (name: string) => boolean; debug: () => void; } interface UploadResult$1 { element: HTMLImageElement; status: boolean; blobInfo: BlobInfo; uploadUri: string; removed: boolean; } interface EditorUpload { blobCache: BlobCache; addFilter: (filter: (img: HTMLImageElement) => boolean) => void; uploadImages: () => Promise; uploadImagesAuto: () => Promise; scanForImages: () => Promise; destroy: () => void; } type FormatChangeCallback = (state: boolean, data: { node: Node; format: string; parents: Element[]; }) => void; interface FormatRegistry { get: { (name: string): Format[] | undefined; (): Record; }; has: (name: string) => boolean; register: (name: string | Formats, format?: Format[] | Format) => void; unregister: (name: string) => Formats; } interface Formatter extends FormatRegistry { apply: (name: string, vars?: FormatVars, node?: Node | RangeLikeObject | null) => void; remove: (name: string, vars?: FormatVars, node?: Node | Range, similar?: boolean) => void; toggle: (name: string, vars?: FormatVars, node?: Node) => void; match: (name: string, vars?: FormatVars, node?: Node, similar?: boolean) => boolean; closest: (names: string[]) => string | null; matchAll: (names: string[], vars?: FormatVars) => string[]; matchNode: (node: Node | null, name: string, vars?: FormatVars, similar?: boolean) => Format | undefined; canApply: (name: string) => boolean; formatChanged: (names: string, callback: FormatChangeCallback, similar?: boolean, vars?: FormatVars) => { unbind: () => void; }; getCssText: (format: string | ApplyFormat) => string; } interface EditorMode { isReadOnly: () => boolean; set: (mode: string) => void; get: () => string; register: (mode: string, api: EditorModeApi) => void; } interface EditorModeApi { activate: () => void; deactivate: () => void; editorReadOnly: boolean; } interface Model { readonly table: { readonly getSelectedCells: () => HTMLTableCellElement[]; readonly clearSelectedCells: (container: Node) => void; }; } type ModelManager = AddOnManager; interface Plugin { getMetadata?: () => { name: string; url: string; }; init?: (editor: Editor, url: string) => void; [key: string]: any; } type PluginManager = AddOnManager; interface ShortcutsConstructor { readonly prototype: Shortcuts; new (editor: Editor): Shortcuts; } type CommandFunc = string | [ string, boolean, any ] | (() => void); declare class Shortcuts { private readonly editor; private readonly shortcuts; private pendingPatterns; constructor(editor: Editor); add(pattern: string, desc: string | null, cmdFunc: CommandFunc, scope?: any): boolean; remove(pattern: string): boolean; private normalizeCommandFunc; private createShortcut; private hasModifier; private isFunctionKey; private matchShortcut; private executeShortcutAction; } interface RenderResult { iframeContainer?: HTMLElement; editorContainer: HTMLElement; api?: Partial; } interface Theme { ui?: any; inline?: any; execCommand?: (command: string, ui?: boolean, value?: any) => boolean; destroy?: () => void; init?: (editor: Editor, url: string) => void; renderUI?: () => Promise | RenderResult; getNotificationManagerImpl?: () => NotificationManagerImpl; getWindowManagerImpl?: () => WindowManagerImpl; } type ThemeManager = AddOnManager; interface EditorConstructor { readonly prototype: Editor; new (id: string, options: RawEditorOptions, editorManager: EditorManager): Editor; } declare class Editor implements EditorObservable { documentBaseUrl: string; baseUri: URI; id: string; plugins: Record; documentBaseURI: URI; baseURI: URI; contentCSS: string[]; contentStyles: string[]; ui: EditorUi; mode: EditorMode; options: Options; editorUpload: EditorUpload; shortcuts: Shortcuts; loadedCSS: Record; editorCommands: EditorCommands; suffix: string; editorManager: EditorManager; hidden: boolean; inline: boolean; hasVisual: boolean; isNotDirty: boolean; annotator: Annotator; bodyElement: HTMLElement | undefined; bookmark: any; composing: boolean; container: HTMLElement; contentAreaContainer: HTMLElement; contentDocument: Document; contentWindow: Window; delegates: Record> | undefined; destroyed: boolean; dom: DOMUtils; editorContainer: HTMLElement; eventRoot: Element | undefined; formatter: Formatter; formElement: HTMLElement | undefined; formEventDelegate: ((e: Event) => void) | undefined; hasHiddenInput: boolean; iframeElement: HTMLIFrameElement | null; iframeHTML: string | undefined; initialized: boolean; notificationManager: NotificationManager; orgDisplay: string; orgVisibility: string | undefined; parser: DomParser; quirks: Quirks; readonly: boolean; removed: boolean; schema: Schema; selection: EditorSelection; serializer: DomSerializer; startContent: string; targetElm: HTMLElement; theme: Theme; model: Model; undoManager: UndoManager; windowManager: WindowManager; _beforeUnload: (() => void) | undefined; _eventDispatcher: EventDispatcher | undefined; _nodeChangeDispatcher: NodeChange; _pendingNativeEvents: string[]; _selectionOverrides: SelectionOverrides; _skinLoaded: boolean; _editableRoot: boolean; bindPendingEventDelegates: EditorObservable['bindPendingEventDelegates']; toggleNativeEvent: EditorObservable['toggleNativeEvent']; unbindAllNativeEvents: EditorObservable['unbindAllNativeEvents']; fire: EditorObservable['fire']; dispatch: EditorObservable['dispatch']; on: EditorObservable['on']; off: EditorObservable['off']; once: EditorObservable['once']; hasEventListeners: EditorObservable['hasEventListeners']; constructor(id: string, options: RawEditorOptions, editorManager: EditorManager); render(): void; focus(skipFocus?: boolean): void; hasFocus(): boolean; translate(text: Untranslated): TranslatedString; getParam(name: string, defaultVal: BuiltInOptionTypeMap[K], type: K): BuiltInOptionTypeMap[K]; getParam(name: K, defaultVal?: NormalizedEditorOptions[K], type?: BuiltInOptionType): NormalizedEditorOptions[K]; getParam(name: string, defaultVal: T, type?: BuiltInOptionType): T; hasPlugin(name: string, loaded?: boolean): boolean; nodeChanged(args?: any): void; addCommand(name: string, callback: EditorCommandCallback, scope: S): void; addCommand(name: string, callback: EditorCommandCallback): void; addQueryStateHandler(name: string, callback: (this: S) => boolean, scope?: S): void; addQueryStateHandler(name: string, callback: (this: Editor) => boolean): void; addQueryValueHandler(name: string, callback: (this: S) => string, scope: S): void; addQueryValueHandler(name: string, callback: (this: Editor) => string): void; addShortcut(pattern: string, desc: string, cmdFunc: string | [ string, boolean, any ] | (() => void), scope?: any): void; execCommand(cmd: string, ui?: boolean, value?: any, args?: ExecCommandArgs): boolean; queryCommandState(cmd: string): boolean; queryCommandValue(cmd: string): string; queryCommandSupported(cmd: string): boolean; show(): void; hide(): void; isHidden(): boolean; setProgressState(state: boolean, time?: number): void; load(args?: Partial): string; save(args?: Partial): string; setContent(content: string, args?: Partial): string; setContent(content: AstNode, args?: Partial): AstNode; setContent(content: Content, args?: Partial): Content; getContent(args: { format: 'tree'; } & Partial): AstNode; getContent(args?: Partial): string; insertContent(content: string, args?: any): void; resetContent(initialContent?: string): void; isDirty(): boolean; setDirty(state: boolean): void; getContainer(): HTMLElement; getContentAreaContainer(): HTMLElement; getElement(): HTMLElement; getWin(): Window; getDoc(): Document; getBody(): HTMLElement; convertURL(url: string, name: string, elm?: string | Element): string; addVisual(elm?: HTMLElement): void; setEditableRoot(state: boolean): void; hasEditableRoot(): boolean; remove(): void; destroy(automatic?: boolean): void; uploadImages(): Promise; _scanForImages(): Promise; } interface UrlObject { prefix: string; resource: string; suffix: string; } type WaitState = 'added' | 'loaded'; type AddOnConstructor = (editor: Editor, url: string) => T; interface AddOnManager { items: AddOnConstructor[]; urls: Record; lookup: Record; }>; get: (name: string) => AddOnConstructor | undefined; requireLangPack: (name: string, languages?: string) => void; add: (id: string, addOn: AddOnConstructor) => AddOnConstructor; remove: (name: string) => void; createUrl: (baseUrl: UrlObject, dep: string | UrlObject) => UrlObject; load: (name: string, addOnUrl: string | UrlObject) => Promise; waitFor: (name: string, state?: WaitState) => Promise; } interface RangeUtils { walk: (rng: Range, callback: (nodes: Node[]) => void) => void; split: (rng: Range) => RangeLikeObject; normalize: (rng: Range) => boolean; expand: (rng: Range, options?: { type: 'word'; }) => Range; } interface ScriptLoaderSettings { referrerPolicy?: ReferrerPolicy; } interface ScriptLoaderConstructor { readonly prototype: ScriptLoader; new (): ScriptLoader; ScriptLoader: ScriptLoader; } declare class ScriptLoader { static ScriptLoader: ScriptLoader; private settings; private states; private queue; private scriptLoadedCallbacks; private queueLoadedCallbacks; private loading; constructor(settings?: ScriptLoaderSettings); _setReferrerPolicy(referrerPolicy: ReferrerPolicy): void; loadScript(url: string): Promise; isDone(url: string): boolean; markDone(url: string): void; add(url: string): Promise; load(url: string): Promise; remove(url: string): void; loadQueue(): Promise; loadScripts(scripts: string[]): Promise; } type TextProcessCallback = (node: Text, offset: number, text: string) => number; interface Spot { container: Text; offset: number; } interface TextSeeker { backwards: (node: Node, offset: number, process: TextProcessCallback, root?: Node) => Spot | null; forwards: (node: Node, offset: number, process: TextProcessCallback, root?: Node) => Spot | null; } interface DomTreeWalkerConstructor { readonly prototype: DomTreeWalker; new (startNode: Node, rootNode: Node): DomTreeWalker; } declare class DomTreeWalker { private readonly rootNode; private node; constructor(startNode: Node, rootNode: Node); current(): Node | null | undefined; next(shallow?: boolean): Node | null | undefined; prev(shallow?: boolean): Node | null | undefined; prev2(shallow?: boolean): Node | null | undefined; private findSibling; private findPreviousNode; } interface Version { major: number; minor: number; } interface Env { transparentSrc: string; documentMode: number; cacheSuffix: any; container: any; canHaveCSP: boolean; windowsPhone: boolean; browser: { current: string | undefined; version: Version; isEdge: () => boolean; isChromium: () => boolean; isIE: () => boolean; isOpera: () => boolean; isFirefox: () => boolean; isSafari: () => boolean; }; os: { current: string | undefined; version: Version; isWindows: () => boolean; isiOS: () => boolean; isAndroid: () => boolean; isMacOS: () => boolean; isLinux: () => boolean; isSolaris: () => boolean; isFreeBSD: () => boolean; isChromeOS: () => boolean; }; deviceType: { isiPad: () => boolean; isiPhone: () => boolean; isTablet: () => boolean; isPhone: () => boolean; isTouch: () => boolean; isWebView: () => boolean; isDesktop: () => boolean; }; } interface FakeClipboardItem { readonly items: Record; readonly types: ReadonlyArray; readonly getType: (type: string) => D | undefined; } interface FakeClipboard { readonly FakeClipboardItem: (items: Record) => FakeClipboardItem; readonly write: (data: FakeClipboardItem[]) => void; readonly read: () => FakeClipboardItem[] | undefined; readonly clear: () => void; } interface FocusManager { isEditorUIElement: (elm: Element) => boolean; } interface EntitiesMap { [name: string]: string; } interface Entities { encodeRaw: (text: string, attr?: boolean) => string; encodeAllRaw: (text: string) => string; encodeNumeric: (text: string, attr?: boolean) => string; encodeNamed: (text: string, attr?: boolean, entities?: EntitiesMap) => string; getEncodeFunc: (name: string, entities?: string) => (text: string, attr?: boolean) => string; decode: (text: string) => string; } interface IconPack { icons: Record; } interface IconManager { add: (id: string, iconPack: IconPack) => void; get: (id: string) => IconPack; has: (id: string) => boolean; } interface Resource { load: (id: string, url: string) => Promise; add: (id: string, data: any) => void; has: (id: string) => boolean; get: (id: string) => any; unload: (id: string) => void; } type TextPatterns_d_Pattern = Pattern; type TextPatterns_d_RawPattern = RawPattern; type TextPatterns_d_DynamicPatternsLookup = DynamicPatternsLookup; type TextPatterns_d_RawDynamicPatternsLookup = RawDynamicPatternsLookup; type TextPatterns_d_DynamicPatternContext = DynamicPatternContext; type TextPatterns_d_BlockCmdPattern = BlockCmdPattern; type TextPatterns_d_BlockPattern = BlockPattern; type TextPatterns_d_BlockFormatPattern = BlockFormatPattern; type TextPatterns_d_InlineCmdPattern = InlineCmdPattern; type TextPatterns_d_InlinePattern = InlinePattern; type TextPatterns_d_InlineFormatPattern = InlineFormatPattern; declare namespace TextPatterns_d { export { TextPatterns_d_Pattern as Pattern, TextPatterns_d_RawPattern as RawPattern, TextPatterns_d_DynamicPatternsLookup as DynamicPatternsLookup, TextPatterns_d_RawDynamicPatternsLookup as RawDynamicPatternsLookup, TextPatterns_d_DynamicPatternContext as DynamicPatternContext, TextPatterns_d_BlockCmdPattern as BlockCmdPattern, TextPatterns_d_BlockPattern as BlockPattern, TextPatterns_d_BlockFormatPattern as BlockFormatPattern, TextPatterns_d_InlineCmdPattern as InlineCmdPattern, TextPatterns_d_InlinePattern as InlinePattern, TextPatterns_d_InlineFormatPattern as InlineFormatPattern, }; } interface Delay { setEditorInterval: (editor: Editor, callback: () => void, time?: number) => number; setEditorTimeout: (editor: Editor, callback: () => void, time?: number) => number; } type UploadResult = UploadResult$2; interface ImageUploader { upload: (blobInfos: BlobInfo[], showNotification?: boolean) => Promise; } type ArrayCallback$1 = (this: any, x: T, i: number, xs: ArrayLike) => R; type ObjCallback$1 = (this: any, value: T, key: string, obj: Record) => R; type ArrayCallback = ArrayCallback$1; type ObjCallback = ObjCallback$1; type WalkCallback = (this: any, o: T, i: string, n: keyof T | undefined) => boolean | void; interface Tools { is: (obj: any, type?: string) => boolean; isArray: (arr: any) => arr is Array; inArray: (arr: ArrayLike, value: T) => number; grep: { (arr: ArrayLike | null | undefined, pred?: ArrayCallback): T[]; (arr: Record | null | undefined, pred?: ObjCallback): T[]; }; trim: (str: string | null | undefined) => string; toArray: (obj: ArrayLike) => T[]; hasOwn: (obj: any, name: string) => boolean; makeMap: (items: ArrayLike | string | undefined, delim?: string | RegExp, map?: Record) => Record; each: { (arr: ArrayLike | null | undefined, cb: ArrayCallback, scope?: any): boolean; (obj: Record | null | undefined, cb: ObjCallback, scope?: any): boolean; }; map: { (arr: ArrayLike | null | undefined, cb: ArrayCallback): R[]; (obj: Record | null | undefined, cb: ObjCallback): R[]; }; extend: (obj: Object, ext: Object, ...objs: Object[]) => any; walk: >(obj: T, f: WalkCallback, n?: keyof T, scope?: any) => void; resolve: (path: string, o?: Object) => any; explode: (s: string | string[], d?: string | RegExp) => string[]; _addCacheSuffix: (url: string) => string; } interface KeyboardLikeEvent { shiftKey: boolean; ctrlKey: boolean; altKey: boolean; metaKey: boolean; } interface VK { BACKSPACE: number; DELETE: number; DOWN: number; ENTER: number; ESC: number; LEFT: number; RIGHT: number; SPACEBAR: number; TAB: number; UP: number; PAGE_UP: number; PAGE_DOWN: number; END: number; HOME: number; modifierPressed: (e: KeyboardLikeEvent) => boolean; metaKeyPressed: (e: KeyboardLikeEvent) => boolean; } interface DOMUtilsNamespace { (doc: Document, settings: Partial): DOMUtils; DOM: DOMUtils; nodeIndex: (node: Node, normalized?: boolean) => number; } interface RangeUtilsNamespace { (dom: DOMUtils): RangeUtils; compareRanges: (rng1: RangeLikeObject, rng2: RangeLikeObject) => boolean; getCaretRangeFromPoint: (clientX: number, clientY: number, doc: Document) => Range; getSelectedNode: (range: Range) => Node; getNode: (container: Node, offset: number) => Node; } interface AddOnManagerNamespace { (): AddOnManager; language: string | undefined; languageLoad: boolean; baseURL: string; PluginManager: PluginManager; ThemeManager: ThemeManager; ModelManager: ModelManager; } interface BookmarkManagerNamespace { (selection: EditorSelection): BookmarkManager; isBookmarkNode: (node: Node) => boolean; } interface TinyMCE extends EditorManager { geom: { Rect: Rect; }; util: { Delay: Delay; Tools: Tools; VK: VK; URI: URIConstructor; EventDispatcher: EventDispatcherConstructor; Observable: Observable; I18n: I18n; LocalStorage: Storage; ImageUploader: ImageUploader; }; dom: { EventUtils: EventUtilsConstructor; TreeWalker: DomTreeWalkerConstructor; TextSeeker: (dom: DOMUtils, isBlockBoundary?: (node: Node) => boolean) => TextSeeker; DOMUtils: DOMUtilsNamespace; ScriptLoader: ScriptLoaderConstructor; RangeUtils: RangeUtilsNamespace; Serializer: (settings: DomSerializerSettings, editor?: Editor) => DomSerializer; ControlSelection: (selection: EditorSelection, editor: Editor) => ControlSelection; BookmarkManager: BookmarkManagerNamespace; Selection: (dom: DOMUtils, win: Window, serializer: DomSerializer, editor: Editor) => EditorSelection; StyleSheetLoader: (documentOrShadowRoot: Document | ShadowRoot, settings: StyleSheetLoaderSettings) => StyleSheetLoader; Event: EventUtils; }; html: { Styles: (settings?: StylesSettings, schema?: Schema) => Styles; Entities: Entities; Node: AstNodeConstructor; Schema: (settings?: SchemaSettings) => Schema; DomParser: (settings?: DomParserSettings, schema?: Schema) => DomParser; Writer: (settings?: WriterSettings) => Writer; Serializer: (settings?: HtmlSerializerSettings, schema?: Schema) => HtmlSerializer; }; AddOnManager: AddOnManagerNamespace; Annotator: (editor: Editor) => Annotator; Editor: EditorConstructor; EditorCommands: EditorCommandsConstructor; EditorManager: EditorManager; EditorObservable: EditorObservable; Env: Env; FocusManager: FocusManager; Formatter: (editor: Editor) => Formatter; NotificationManager: (editor: Editor) => NotificationManager; Shortcuts: ShortcutsConstructor; UndoManager: (editor: Editor) => UndoManager; WindowManager: (editor: Editor) => WindowManager; DOM: DOMUtils; ScriptLoader: ScriptLoader; PluginManager: PluginManager; ThemeManager: ThemeManager; ModelManager: ModelManager; IconManager: IconManager; Resource: Resource; FakeClipboard: FakeClipboard; trim: Tools['trim']; isArray: Tools['isArray']; is: Tools['is']; toArray: Tools['toArray']; makeMap: Tools['makeMap']; each: Tools['each']; map: Tools['map']; grep: Tools['grep']; inArray: Tools['inArray']; extend: Tools['extend']; walk: Tools['walk']; resolve: Tools['resolve']; explode: Tools['explode']; _addCacheSuffix: Tools['_addCacheSuffix']; } declare const tinymce: TinyMCE; export { AddOnManager, Annotator, AstNode, Bookmark, BookmarkManager, ControlSelection, DOMUtils, Delay, DomParser, DomParserSettings, DomSerializer, DomSerializerSettings, DomTreeWalker, Editor, EditorCommands, EditorEvent, EditorManager, EditorModeApi, EditorObservable, EditorOptions, EditorSelection, Entities, Env, EventDispatcher, EventUtils, EventTypes_d as Events, FakeClipboard, FocusManager, Format_d as Formats, Formatter, GeomRect, HtmlSerializer, HtmlSerializerSettings, I18n, IconManager, Model, ModelManager, NotificationApi, NotificationManager, NotificationSpec, Observable, Plugin, PluginManager, RangeUtils, RawEditorOptions, Rect, Resource, Schema, SchemaSettings, ScriptLoader, Shortcuts, StyleSheetLoader, Styles, TextPatterns_d as TextPatterns, TextSeeker, Theme, ThemeManager, TinyMCE, Tools, URI, Ui_d as Ui, UndoManager, VK, WindowManager, Writer, WriterSettings, tinymce as default }; ================================================ FILE: apps/web-antd/src/adapter/component/index.ts ================================================ /** * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, */ import type { Component } from 'vue'; import type { BaseFormComponentType } from '@vben/common-ui'; import type { Recordable } from '@vben/types'; import { computed, defineAsyncComponent, defineComponent, h, ref } from 'vue'; import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { notification } from 'ant-design-vue'; import { FileUploadOld, ImageUploadOld } from '#/components/upload-old'; const RichTextarea = defineAsyncComponent(() => import('#/components/tinymce/index').then((res) => res.Tinymce), ); const FileUpload = defineAsyncComponent(() => import('#/components/upload').then((res) => res.FileUpload), ); const ImageUpload = defineAsyncComponent(() => import('#/components/upload').then((res) => res.ImageUpload), ); const AutoComplete = defineAsyncComponent( () => import('ant-design-vue/es/auto-complete'), ); const Button = defineAsyncComponent(() => import('ant-design-vue/es/button')); const Cascader = defineAsyncComponent( () => import('ant-design-vue/es/cascader'), ); const Checkbox = defineAsyncComponent( () => import('ant-design-vue/es/checkbox'), ); const CheckboxGroup = defineAsyncComponent(() => import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup), ); const DatePicker = defineAsyncComponent( () => import('ant-design-vue/es/date-picker'), ); const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider')); const Input = defineAsyncComponent(() => import('ant-design-vue/es/input')); const InputNumber = defineAsyncComponent( () => import('ant-design-vue/es/input-number'), ); const InputPassword = defineAsyncComponent(() => import('ant-design-vue/es/input').then((res) => res.InputPassword), ); const Mentions = defineAsyncComponent( () => import('ant-design-vue/es/mentions'), ); const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio')); const RadioGroup = defineAsyncComponent(() => import('ant-design-vue/es/radio').then((res) => res.RadioGroup), ); const RangePicker = defineAsyncComponent(() => import('ant-design-vue/es/date-picker').then((res) => res.RangePicker), ); const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate')); const Select = defineAsyncComponent(() => import('ant-design-vue/es/select')); const Space = defineAsyncComponent(() => import('ant-design-vue/es/space')); const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch')); const Textarea = defineAsyncComponent(() => import('ant-design-vue/es/input').then((res) => res.Textarea), ); const TimePicker = defineAsyncComponent( () => import('ant-design-vue/es/time-picker'), ); const TimeRangePicker = defineAsyncComponent(() => import('ant-design-vue/es/time-picker').then((res) => res.TimeRangePicker), ); const TreeSelect = defineAsyncComponent( () => import('ant-design-vue/es/tree-select'), ); const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload')); const withDefaultPlaceholder = ( component: T, type: 'input' | 'select', componentProps: Recordable = {}, ) => { return defineComponent({ name: component.name, inheritAttrs: false, setup: (props: any, { attrs, expose, slots }) => { // 改为placeholder 解决在keepalive & 语言切换 & tab切换 显示不变的问题 const computedPlaceholder = computed( () => props?.placeholder || attrs?.placeholder || $t(`ui.placeholder.${type}`), ); // 透传组件暴露的方法 const innerRef = ref(); expose( new Proxy( {}, { get: (_target, key) => innerRef.value?.[key], has: (_target, key) => key in (innerRef.value || {}), }, ), ); return () => h( component, { ...componentProps, placeholder: computedPlaceholder.value, ...props, ...attrs, ref: innerRef, }, slots, ); }, }); }; // 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 export type ComponentType = | 'ApiSelect' | 'ApiTreeSelect' | 'AutoComplete' | 'Cascader' | 'Checkbox' | 'CheckboxGroup' | 'DatePicker' | 'DefaultButton' | 'Divider' | 'FileUpload' | 'FileUploadOld' | 'IconPicker' | 'ImageUpload' | 'ImageUploadOld' | 'Input' | 'InputNumber' | 'InputPassword' | 'Mentions' | 'PrimaryButton' | 'Radio' | 'RadioGroup' | 'RangePicker' | 'Rate' | 'RichTextarea' | 'Select' | 'Space' | 'Switch' | 'Textarea' | 'TimePicker' | 'TimeRangePicker' | 'TreeSelect' | 'Upload' | BaseFormComponentType; async function initComponentAdapter() { const components: Partial> = { // 如果你的组件体积比较大,可以使用异步加载 // Button: () => // import('xxx').then((res) => res.Button), ApiSelect: withDefaultPlaceholder( { ...ApiComponent, name: 'ApiSelect', }, 'select', { component: Select, loadingSlot: 'suffixIcon', visibleEvent: 'onDropdownVisibleChange', modelPropName: 'value', }, ), ApiTreeSelect: withDefaultPlaceholder( { ...ApiComponent, name: 'ApiTreeSelect', }, 'select', { component: TreeSelect, fieldNames: { label: 'label', value: 'value', children: 'children' }, loadingSlot: 'suffixIcon', modelPropName: 'value', optionsPropName: 'treeData', visibleEvent: 'onVisibleChange', }, ), AutoComplete, Cascader: withDefaultPlaceholder(Cascader, 'select'), Checkbox, CheckboxGroup, DatePicker, // 自定义默认按钮 DefaultButton: (props, { attrs, slots }) => { return h(Button, { ...props, attrs, type: 'default' }, slots); }, Divider, IconPicker: withDefaultPlaceholder(IconPicker, 'select', { iconSlot: 'addonAfter', inputComponent: Input, modelValueProp: 'value', }), Input: withDefaultPlaceholder(Input, 'input'), InputNumber: withDefaultPlaceholder(InputNumber, 'input'), InputPassword: withDefaultPlaceholder(InputPassword, 'input'), Mentions: withDefaultPlaceholder(Mentions, 'input'), // 自定义主要按钮 PrimaryButton: (props, { attrs, slots }) => { return h(Button, { ...props, attrs, type: 'primary' }, slots); }, Radio, RadioGroup, RangePicker, Rate, Select: withDefaultPlaceholder(Select, 'select'), Space, Switch, Textarea: withDefaultPlaceholder(Textarea, 'input'), TimePicker, TimeRangePicker, TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), Upload, ImageUpload, FileUpload, RichTextarea, ImageUploadOld, FileUploadOld, }; // 将组件注册到全局共享状态中 globalShareState.setComponents(components); // 定义全局共享状态中的消息提示 globalShareState.defineMessage({ // 复制成功消息提示 copyPreferencesSuccess: (title, content) => { notification.success({ description: content, message: title, placement: 'bottomRight', }); }, }); } export { initComponentAdapter }; ================================================ FILE: apps/web-antd/src/adapter/form.ts ================================================ import type { VbenFormSchema as FormSchema, VbenFormProps, } from '@vben/common-ui'; import type { ComponentType } from './component'; import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; import { $t } from '@vben/locales'; import { isArray } from 'lodash-es'; async function initSetupVbenForm() { setupVbenForm({ config: { // ant design vue组件库默认都是 v-model:value baseModelPropName: 'value', // 一些组件是 v-model:checked 或者 v-model:fileList modelPropNameMap: { Checkbox: 'checked', Radio: 'checked', RichTextarea: 'modelValue', Switch: 'checked', Upload: 'fileList', }, }, defineRules: { // 输入项目必填国际化适配 required: (value, _params, ctx) => { if (value === undefined || value === null || value.length === 0) { return $t('ui.formRules.required', [ctx.label]); } return true; }, // 选择项目必填国际化适配 selectRequired: (value, _params, ctx) => { if ( [false, null, undefined].includes(value) || (isArray(value) && value.length === 0) ) { return $t('ui.formRules.selectRequired', [ctx.label]); } return true; }, }, }); } const useVbenForm = useForm; export { initSetupVbenForm, useVbenForm, z }; export type VbenFormSchema = FormSchema; export type { VbenFormProps }; export type FormSchemaGetter = () => VbenFormSchema[]; ================================================ FILE: apps/web-antd/src/adapter/vxe-table.ts ================================================ import type { VxeGridPropTypes } from '@vben/plugins/vxe-table'; import { h } from 'vue'; import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; import { Button, Image } from 'ant-design-vue'; import { useVbenForm } from './form'; setupVbenVxeTable({ configVxeTable: (vxeUI) => { vxeUI.setConfig({ grid: { align: 'center', // https://vxetable.cn/#/component/table/base/border border: 'inner', minHeight: 180, formConfig: { // 全局禁用vxe-table的表单配置,使用formOptions enabled: false, }, proxyConfig: { autoLoad: true, response: { result: 'rows', total: 'total', list: 'rows', }, showActiveMsg: true, showResponseMsg: false, }, // 溢出展示形式 showOverflow: true, pagerConfig: { // 默认条数 pageSize: 10, // 分页可选条数 pageSizes: [10, 20, 30, 40, 50], }, rowConfig: { // 鼠标移入行显示 hover 样式 isHover: true, // 点击行高亮 isCurrent: false, }, columnConfig: { // 可拖拽列宽 resizable: true, }, // 右上角工具栏 toolbarConfig: { // 自定义列 custom: true, customOptions: { icon: 'vxe-icon-setting', }, // 最大化 zoom: true, // 刷新 refresh: true, refreshOptions: { // 默认为reload 修改为在当前页刷新 code: 'query', }, }, // 圆角按钮 round: true, // 表格尺寸 size: 'medium', customConfig: { // 表格右上角自定义列配置 是否保存到localStorage // 必须存在id参数才能使用 storage: false, }, }, }); // 表格配置项可以用 cellRender: { name: 'CellImage' }, vxeUI.renderer.add('CellImage', { renderTableDefault(_renderOpts, params) { const { column, row } = params; return h(Image, { src: row[column.field] }); }, }); // 表格配置项可以用 cellRender: { name: 'CellLink' }, vxeUI.renderer.add('CellLink', { renderTableDefault(renderOpts) { const { props } = renderOpts; return h( Button, { size: 'small', type: 'link' }, { default: () => props?.text }, ); }, }); // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 // vxeUI.formats.add }, useVbenForm, }); export { useVbenVxeGrid }; export type * from '@vben/plugins/vxe-table'; /** * 判断vxe-table的复选框是否选中 * @param tableApi api * @returns boolean */ export function vxeCheckboxChecked( tableApi: ReturnType[1], ) { return tableApi?.grid?.getCheckboxRecords?.()?.length > 0; } /** * 通用的 排序参数添加到请求参数中 * @param params 请求参数 * @param sortList vxe-table的排序参数 */ export function addSortParams( params: Record, sortList: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[], ) { // 这里是排序取消 length为0 就不添加参数了 if (sortList.length === 0) { return; } // 支持单/多字段排序 const orderByColumn = sortList.map((item) => item.field).join(','); const isAsc = sortList.map((item) => item.order).join(','); params.orderByColumn = orderByColumn; params.isAsc = isAsc; } ================================================ FILE: apps/web-antd/src/api/aiflow/API文档.md ================================================ # 工作流管理 API 接口文档 ## 1. 获取工作流详情 ### 基本信息 - **接口URL**: `/workflow/get/{uuid}` - **请求方式**: `GET` - **接口描述**: 根据工作流UUID获取工作流的完整信息,包括节点、连线、配置等 - **权限要求**: 需要登录,只能获取自己创建的工作流或公开的工作流 ### 请求参数 #### 路径参数 | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | uuid | String | 是 | 工作流的唯一标识符 | #### 请求示例 ```http GET /workflow/get/workflow-1698745623456 HTTP/1.1 Host: api.example.com Authorization: Bearer {token} ``` ### 响应数据 #### 响应结构 ```typescript { code: number; // 状态码,200表示成功 msg: string; // 响应消息 data: { uuid: string; // 工作流UUID title: string; // 工作流名称 remark: string; // 备注说明 isPublic: boolean; // 是否公开 nodes: Array<{ // 节点列表 uuid: string; // 节点UUID title: string; // 节点名称 workflowUuid: string; // 所属工作流UUID workflowComponentId: number; // 工作流组件ID wfComponent: { // 工作流组件信息 name: string; // 组件名称(如:Start、Answer等) title: string; // 组件显示名称 remark: string; // 组件说明 isEnable: boolean; // 是否启用 }; inputConfig: { // 输入配置 user_inputs: Array; // 用户输入配置 ref_inputs: Array; // 引用输入配置 }; nodeConfig: { // 节点配置(根据不同组件类型有不同字段) [key: string]: any; }; outputConfig: { // 输出配置 [key: string]: any; }; positionX: number; // 节点X坐标 positionY: number; // 节点Y坐标 }>; edges: Array<{ // 连线列表 uuid: string; // 连线UUID source: string; // 源节点UUID target: string; // 目标节点UUID sourceHandle?: string; // 源节点连接点ID(可选) targetHandle?: string; // 目标节点连接点ID(可选) label?: string; // 连线标签(可选) }>; createTime: string; // 创建时间(ISO 8601格式) updateTime: string; // 更新时间(ISO 8601格式) createBy: string; // 创建人 updateBy: string; // 更新人 }; } ``` #### 响应示例 ```json { "code": 200, "msg": "操作成功", "data": { "uuid": "workflow-1698745623456", "title": "客服问答工作流", "remark": "用于处理客户常见问题", "isPublic": false, "nodes": [ { "uuid": "node-start-1", "title": "开始", "workflowUuid": "workflow-1698745623456", "workflowComponentId": 1, "wfComponent": { "name": "Start", "title": "开始", "remark": "工作流起始节点", "isEnable": true }, "inputConfig": { "user_inputs": [ { "uuid": "input-1", "name": "question", "title": "用户问题", "type": 1, "required": true } ], "ref_inputs": [] }, "nodeConfig": { "prologue": "您好,请问有什么可以帮您?" }, "outputConfig": {}, "positionX": 100, "positionY": 200 }, { "uuid": "node-answer-1", "title": "AI回答", "workflowUuid": "workflow-1698745623456", "workflowComponentId": 3, "wfComponent": { "name": "Answer", "title": "回答", "remark": "AI生成回答", "isEnable": true }, "inputConfig": { "user_inputs": [], "ref_inputs": [ { "source": "node-start-1", "field": "question" } ] }, "nodeConfig": { "model_name": "gpt-4", "prompt": "请根据用户问题给出专业回答:{{question}}" }, "outputConfig": {}, "positionX": 400, "positionY": 200 } ], "edges": [ { "uuid": "edge-1", "source": "node-start-1", "target": "node-answer-1", "sourceHandle": "right", "targetHandle": "left" } ], "createTime": "2024-10-30T10:30:00Z", "updateTime": "2024-10-30T15:45:00Z", "createBy": "admin", "updateBy": "admin" } } ``` #### 错误响应 ```json { "code": 404, "msg": "工作流不存在", "data": null } ``` ```json { "code": 403, "msg": "无权限访问该工作流", "data": null } ``` ### 注意事项 1. **权限控制**: - 用户只能获取自己创建的工作流 - 公开的工作流所有登录用户都可以获取 2. **数据完整性**: - 返回的工作流包含完整的节点和连线信息 - 节点配置 `nodeConfig` 根据不同组件类型会有不同的字段结构 3. **坐标系统**: - `positionX` 和 `positionY` 是节点在画布上的位置坐标 - 单位为像素(px) --- ## 2. 分页查询工作流列表 ### 基本信息 - **接口URL**: `/workflow/page` - **请求方式**: `POST` - **接口描述**: 分页查询工作流列表,支持按标题搜索和类型过滤 - **权限要求**: 需要登录 ### 请求参数 #### Body参数(JSON格式) | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | pageNum | Integer | 是 | 页码,从1开始 | | pageSize | Integer | 是 | 每页数量,建议10-100 | | title | String | 否 | 工作流名称,支持模糊搜索 | | type | String | 否 | 类型筛选:空字符串或不传=全部,"my"=我的,"public"=公开 | #### 请求示例 ```http POST /workflow/page HTTP/1.1 Host: api.example.com Content-Type: application/json Authorization: Bearer {token} { "pageNum": 1, "pageSize": 10, "title": "客服", "type": "my" } ``` ### 响应数据 #### 响应结构 ```typescript { code: number; // 状态码,200表示成功 msg: string; // 响应消息 data: { total: number; // 总记录数 rows: Array<{ // 工作流列表 uuid: string; // 工作流UUID title: string; // 工作流名称 remark: string; // 备注说明 isPublic: boolean; // 是否公开 nodes: Array; // 节点列表(简化版,仅用于统计数量) edges: Array; // 连线列表(简化版) createTime: string; // 创建时间 updateTime: string; // 更新时间 createBy: string; // 创建人 updateBy: string; // 更新人 }>; }; } ``` #### 响应示例 ```json { "code": 200, "msg": "操作成功", "data": { "total": 25, "rows": [ { "uuid": "workflow-1698745623456", "title": "客服问答工作流", "remark": "用于处理客户常见问题", "isPublic": false, "nodes": [ { "uuid": "node-start-1" }, { "uuid": "node-answer-1" }, { "uuid": "node-end-1" } ], "edges": [ { "uuid": "edge-1" }, { "uuid": "edge-2" } ], "createTime": "2024-10-30T10:30:00Z", "updateTime": "2024-10-30T15:45:00Z", "createBy": "admin", "updateBy": "admin" }, { "uuid": "workflow-1698745623457", "title": "客服智能路由", "remark": "根据问题类型自动分配", "isPublic": true, "nodes": [ { "uuid": "node-start-2" }, { "uuid": "node-classifier-1" }, { "uuid": "node-switcher-1" } ], "edges": [ { "uuid": "edge-3" } ], "createTime": "2024-10-29T14:20:00Z", "updateTime": "2024-10-29T14:20:00Z", "createBy": "admin", "updateBy": "admin" } ] } } ``` #### 错误响应 ```json { "code": 400, "msg": "参数错误:pageNum必须大于0", "data": null } ``` ```json { "code": 400, "msg": "参数错误:pageSize必须在1-100之间", "data": null } ``` ### 注意事项 1. **分页参数**: - `pageNum` 从1开始,不是0 - `pageSize` 建议范围 1-100,超出范围应返回400错误 2. **搜索功能**: - `title` 参数支持模糊搜索(LIKE '%title%') - 搜索不区分大小写 3. **类型过滤**: - `type` 为空或不传:返回所有工作流(我的+公开) - `type="my"`:仅返回当前用户创建的工作流 - `type="public"`:仅返回公开的工作流 4. **数据优化**: - 列表接口中的 `nodes` 和 `edges` 为简化版,仅包含基本信息 - 前端会根据数组长度统计节点数量,无需额外返回 `nodeCount` 字段 5. **排序规则**: - 默认按 `updateTime` 降序排列(最新修改的在前) - 可根据需求添加其他排序字段 --- ================================================ FILE: apps/web-antd/src/api/aiflow/adapters.ts ================================================ import { requestClient } from '#/api/request' export const adapters = { httpGet(url: string, config?: any) { return requestClient.get(url, config) }, httpPost(url: string, data?: any, config?: any) { return requestClient.post(url, data, config) }, httpPut(url: string, data?: any, config?: any) { return requestClient.put(url, data, config) }, httpDelete(url: string, config?: any) { return requestClient.delete(url, config) } } ================================================ FILE: apps/web-antd/src/api/aiflow/index.ts ================================================ import { adapters } from './adapters' export const workflowApi = { workflowAdd(data: { title: string; remark: string; isPublic: boolean }) { return adapters.httpPost('/workflow/add', data) }, workflowCopy(wfUuid: string) { return adapters.httpPost(`/workflow/copy/${wfUuid}`) }, workflowUpdate(data: Workflow.WorkflowUpdateReq) { return adapters.httpPost('/workflow/update', data) }, workflowDel(uuid: string) { return adapters.httpPost(`/workflow/del/${uuid}`) }, workflowSetPublic(uuid: string, isPublic?: boolean) { return adapters.httpPost(`/workflow/set-public/${uuid}?isPublic=${isPublic}`) }, workflowBaseInfoUpdate(data: { uuid: string; title: string; remark: string; isPublic: boolean }) { return adapters.httpPost('/workflow/base-info/update', data) }, //新增节点 addNode(data: { name: string; title: string }) { return adapters.httpPost('/admin/workflow/component/addOrUpdate', data) }, // 上传文件 uploadFile(data: File) { const formData = new FormData(); formData.append('files', data); return adapters.httpPost('/resource/oss/fileUpload', formData); }, workflowGet(uuid: string) { return adapters.httpGet(`/workflow/${uuid}`) }, workflowPage(params: { currentPage: number; pageSize: number; wfSearchReq: { title?: string; isEnable?: boolean; isPublic?: boolean; } }) { const { currentPage, pageSize, wfSearchReq } = params return adapters.httpPost('/admin/workflow/search', wfSearchReq, { params: { currentPage, pageSize } }) }, workflowComponents() { return adapters.httpGet('/workflow/public/component/list') }, workflowSearchMine(keyword: string, currentPage: number, pageSize: number) { const search = keyword === undefined ? '' : `keyword=${keyword}&` return adapters.httpGet(`/workflow/mine/search?${search}currentPage=${currentPage}&pageSize=${pageSize}`) }, workflowSearchPublic(keyword: string, currentPage: number, pageSize: number) { const search = keyword === undefined ? '' : `keyword=${keyword}&` return adapters.httpGet(`/workflow/public/search?${search}currentPage=${currentPage}&pageSize=${pageSize}`) }, workflowRuntimes(wfUuid: string, currentPage: number, pageSize: number) { return adapters.httpGet(`/workflow/runtime/page?wfUuid=${wfUuid}¤tPage=${currentPage}&pageSize=${pageSize}`) }, workflowRuntimeNodes(wfRuntimeUuid: string) { return adapters.httpGet(`/workflow/runtime/nodes/${wfRuntimeUuid}`) }, workflowRuntimesClear() { return adapters.httpPost('/workflow/runtime/clear') }, workflowOperators() { return adapters.httpGet('/workflow/public/operators') }, workflowRuntimeDelete(wfRuntimeUuid: string) { return adapters.httpGet(`/workflow/runtime/del/${wfRuntimeUuid}`) }, workflowRuntimeResume(params: { runtimeUuid: string feedbackContent: string }) { return adapters.httpPost(`/workflow/runtime/resume/${params.runtimeUuid}`, { ...params }) }, } export default workflowApi // 运行时能力透出(便于按包内其他接口形式统一从此处导出) export * from './runtime' ================================================ FILE: apps/web-antd/src/api/aiflow/runtime.ts ================================================ // 运行时相关接口(提供注入能力,避免与应用层 API 耦合) import { adapters } from './adapters' import { useAccessStore } from '@vben/stores' export interface WorkflowRunParams { options: { uuid: string; inputs: any[] } signal?: AbortSignal startCallback?: (wfRuntimeJson: string) => void thinkingDataReceived?: (chunk: string) => void messageReceived?: (chunk: string, event?: string) => void doneCallback?: (finalChunk: string) => void errorCallback?: (error: string) => void } export interface WorkflowResumeParams { runtimeUuid: string feedbackContent: string } let workflowRunImpl: ((p: WorkflowRunParams) => Promise) | null = null let workflowResumeImpl: ((p: WorkflowResumeParams) => Promise) | null = null export function setWorkflowRunImpl(fn: (p: WorkflowRunParams) => Promise) { workflowRunImpl = fn } export function setWorkflowResumeImpl(fn: (p: WorkflowResumeParams) => Promise) { workflowResumeImpl = fn } export async function workflowRun(p: WorkflowRunParams) { if (!workflowRunImpl) { // 默认回退到真实接口(后端改为 body 传 uuid/inputs,URL 不再携带 uuid) return commonSseProcess(`/api/workflow/run`, { options: p.options, signal: p.signal, startCallback: p.startCallback || (() => {}), messageReceived: p.messageReceived || (() => {}), thinkingDataReceived: p.thinkingDataReceived || (() => {}), doneCallback: p.doneCallback || (() => {}), errorCallback: p.errorCallback || (() => {}), }) } return workflowRunImpl(p) } export async function workflowRuntimeResume(p: WorkflowResumeParams) { if (!workflowResumeImpl) { return adapters.httpPost(`/workflow/runtime/resume/${p.runtimeUuid}`, { ...p }) } return workflowResumeImpl(p) } let uploadAction = '/api/file/upload' export function setUploadAction(url: string) { uploadAction = url } export function getUploadAction() { return uploadAction } // 标准化的 SSE 运行器(不包含任何权限处理) export async function commonSseProcess( url: string, params: { options: any signal?: AbortSignal startCallback: (chunk: string) => void thinkingDataReceived: (chunk: string) => void messageReceived: (chunk: string, eventName: string) => void audioDataReceived?: (chunk: string) => void stateChanged?: (state: string) => void doneCallback: (chunk: string) => void errorCallback: (error: string) => void }, ) { try { // 使用项目的请求客户端来获取正确的配置 const accessStore = useAccessStore() const token = accessStore.accessToken ? `Bearer ${accessStore.accessToken}` : '' console.log('SSE 请求配置:', { url, token: token ? '已设置' : '未设置', options: params.options }) const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': token, 'Accept-Language': 'zh_CN', 'Content-Language': 'zh_CN', 'clientId': 'web' }, body: JSON.stringify({ ...params.options }), signal: params.signal, }) console.log('SSE 响应状态:', { status: res.status, statusText: res.statusText, contentType: res.headers.get('content-type'), ok: res.ok }) const contentType = res.headers.get('content-type') || '' if (!res.ok || !contentType.includes('text/event-stream')) { throw new Error(`SSE open failed: ${res.status} ${res.statusText}`) } const reader = res.body?.getReader() if (!reader) { throw new Error('ReadableStream not supported') } const decoder = new TextDecoder('utf-8') let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) // 按事件块分割(以空行分隔) const parts = buffer.split('\n\n') // 最后一段可能是不完整,保留在 buffer buffer = parts.pop() || '' for (const part of parts) { // 解析 sse 行:event: xxx / data: yyy const lines = part.split('\n') let eventName = '' const dataLines: string[] = [] for (const line of lines) { if (line.startsWith('event:')) eventName = line.slice(6).trim() else if (line.startsWith('data:')) dataLines.push(line.slice(5)) } let data = dataLines.join('\n') if (data.indexOf('-_wrap_-') === 0) data = data.replace('-_wrap_-', '\n') // 分发内置事件 if (eventName === '[START]') { params.startCallback(data); continue } if (eventName === '[ERROR]') { params.errorCallback(data); continue } if (eventName === '[DONE]') { params.doneCallback(data); continue } if (eventName === '[AUDIO]') { params.audioDataReceived && params.audioDataReceived(data); continue } if (eventName === '[THINKING]') { params.thinkingDataReceived && params.thinkingDataReceived(data); continue } if (eventName === '[STATE_CHANGED]') { params.stateChanged && params.stateChanged(data); continue } params.messageReceived(data, eventName) } } } catch (e: any) { params.errorCallback(e?.message || String(e)) throw e } } // 运行记录相关 export function workflowRuntimes(wfUuid: string, currentPage: number, pageSize: number) { return adapters.httpGet(`/workflow/runtime/page?wfUuid=${wfUuid}¤tPage=${currentPage}&pageSize=${pageSize}`) } export function workflowRuntimeNodes(wfRuntimeUuid: string) { return adapters.httpGet(`/workflow/runtime/nodes/${wfRuntimeUuid}`) } export function workflowRuntimesClear() { return adapters.httpPost('/workflow/runtime/clear') } export function workflowRuntimeDelete(uuid: string) { return adapters.httpPost(`/workflow/runtime/del/${uuid}`) } ================================================ FILE: apps/web-antd/src/api/aiflow/types.d.ts ================================================ declare namespace Workflow { interface WorkflowUpdateReq { uuid: string title: string remark: string isPublic: boolean nodes: any[] edges: any[] } interface WorkflowInfo { uuid: string title: string remark: string isPublic: boolean nodes: WorkflowNode[] edges: WorkflowEdge[] } interface WorkflowNode { uuid: string title: string workflowUuid: string wfComponent: WorkflowComponent inputConfig: any nodeConfig: any outputConfig: any positionX: number positionY: number } interface WorkflowEdge { id?: string uuid: string workflowUuid: string sourceNodeUuid: string sourceHandle?: string targetNodeUuid: string } interface WorkflowComponent { name: string title: string remark?: string isEnable?: boolean } interface WorkflowRuntime { uuid: string workflowUuid: string status: string createTime: string updateTime: string } interface WorkflowRuntimeNode { uuid: string runtimeUuid: string nodeUuid: string status: string inputData: any outputData: any errorMessage?: string } } ================================================ FILE: apps/web-antd/src/api/chat/chatconfig/index.ts ================================================ import type { ConfigForm, ConfigQuery, ConfigVO } from './model'; import type { ID, IDS, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** * 查询配置信息列表 * @param params * @returns 配置信息列表 */ export function configList(params?: ConfigQuery) { return requestClient.get>('/system/config/list', { params, }); } /** * 导出配置信息列表 * @param params * @returns 配置信息列表 */ export function configExport(params?: ConfigQuery) { return commonExport('/system/config/export', params ?? {}); } /** * 查询配置信息详情 * @param id id * @returns 配置信息详情 */ export function configInfo(id: ID) { return requestClient.get(`/system/config/${id}`); } /** * 新增配置信息 * @param data * @returns void */ export function configAdd(data: ConfigForm) { return requestClient.postWithMsg('/system/config', data); } /** * 更新配置信息 * @param data * @returns void */ export function configUpdate(data: ConfigForm) { return requestClient.putWithMsg('/system/config', data); } /** * 删除配置信息 * @param id id * @returns void */ export function configRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/system/config/${id}`); } ================================================ FILE: apps/web-antd/src/api/chat/chatconfig/model.d.ts ================================================ import type { BaseEntity, PageQuery } from '#/api/common'; export interface ConfigVO { /** * 主键 */ id: number | string; /** * 配置类型 */ category: string; /** * 配置名称 */ configName: string; /** * 配置值 */ configValue: string; /** * 说明 */ configDict: string; /** * 备注 */ remark: string; /** * 更新IP */ updateIp: string; } export interface ConfigForm extends BaseEntity { /** * 主键 */ id?: number | string; /** * 配置类型 */ category?: string; /** * 配置名称 */ configName?: string; /** * 配置值 */ configValue?: string; /** * 说明 */ configDict?: string; /** * 备注 */ remark?: string; /** * 更新IP */ updateIp?: string; } export interface ConfigQuery extends PageQuery { /** * 配置类型 */ category?: string; /** * 配置名称 */ configName?: string; /** * 配置值 */ configValue?: string; /** * 说明 */ configDict?: string; /** * 更新IP */ updateIp?: string; /** * 日期范围参数 */ params?: any; } ================================================ FILE: apps/web-antd/src/api/chat/message/index.ts ================================================ import type { MessageForm, MessageQuery, MessageVO } from './model'; import type { ID, IDS, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** * 查询聊天消息列表 * @param params * @returns 聊天消息列表 */ export function messageList(params?: MessageQuery) { return requestClient.get>('/system/message/list', { params, }); } /** * 导出聊天消息列表 * @param params * @returns 聊天消息列表 */ export function messageExport(params?: MessageQuery) { return commonExport('/system/message/export', params ?? {}); } /** * 查询聊天消息详情 * @param id id * @returns 聊天消息详情 */ export function messageInfo(id: ID) { return requestClient.get(`/system/message/${id}`); } /** * 新增聊天消息 * @param data * @returns void */ export function messageAdd(data: MessageForm) { return requestClient.postWithMsg('/system/message', data); } /** * 更新聊天消息 * @param data * @returns void */ export function messageUpdate(data: MessageForm) { return requestClient.putWithMsg('/system/message', data); } /** * 删除聊天消息 * @param id id * @returns void */ export function messageRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/system/message/${id}`); } ================================================ FILE: apps/web-antd/src/api/chat/message/model.d.ts ================================================ import type { BaseEntity, PageQuery } from '#/api/common'; export interface MessageVO { /** * 主键 */ id: number | string; /** * 会话id */ sessionId: number | string; /** * 用户id */ userId: number | string; /** * 消息内容 */ content: string; /** * 对话角色 */ role: string; /** * 累计 Tokens */ totalTokens: number; /** * 模型名称 */ modelName: string; /** * 备注 */ remark: string; } export interface MessageForm extends BaseEntity { /** * 主键 */ id?: number | string; /** * 会话id */ sessionId?: number | string; /** * 用户id */ userId?: number | string; /** * 消息内容 */ content?: string; /** * 对话角色 */ role?: string; /** * 累计 Tokens */ totalTokens?: number; /** * 模型名称 */ modelName?: string; /** * 备注 */ remark?: string; } export interface MessageQuery extends PageQuery { /** * 会话id */ sessionId?: number | string; /** * 用户id */ userId?: number | string; /** * 消息内容 */ content?: string; /** * 对话角色 */ role?: string; /** * 累计 Tokens */ totalTokens?: number; /** * 模型名称 */ modelName?: string; /** * 日期范围参数 */ params?: any; } ================================================ FILE: apps/web-antd/src/api/chat/model/index.ts ================================================ import type { ModelForm, ModelQuery, ModelVO } from './model'; import type { ID, IDS, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** * 查询模型管理列表 * @param params * @returns 模型管理列表 */ export function modelList(params?: ModelQuery) { return requestClient.get>('/system/model/list', { params, }); } /** * 查询向量模型列表 * @returns 向量模型列表 */ export function embeddingModelList() { return modelList({ category: 'vector', pageSize: 1000 }); } /** * 查询重排序模型列表 * @returns 重排序模型列表 */ export function rerankModelList() { return modelList({ category: 'rerank', pageSize: 1000 }); } /** * 导出模型管理列表 * @param params * @returns 模型管理列表 */ export function modelExport(params?: ModelQuery) { return commonExport('/system/model/export', params ?? {}); } /** * 查询模型管理详情 * @param id id * @returns 模型管理详情 */ export function modelInfo(id: ID) { return requestClient.get(`/system/model/${id}`); } /** * 新增模型管理 * @param data * @returns void */ export function modelAdd(data: ModelForm) { return requestClient.postWithMsg('/system/model', data); } /** * 更新模型管理 * @param data * @returns void */ export function modelUpdate(data: ModelForm) { return requestClient.putWithMsg('/system/model', data); } /** * 删除模型管理 * @param id id * @returns void */ export function modelRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/system/model/${id}`); } ================================================ FILE: apps/web-antd/src/api/chat/model/model.d.ts ================================================ import type { BaseEntity, PageQuery } from '#/api/common'; export interface ModelVO { /** * 主键 */ id: number | string; /** * 模型分类 */ category: string; /** * 模型名称 */ modelName: string; /** * 模型供应商 */ providerCode: number | string; /** * 模型描述 */ modelDescribe: string; /** * 是否显示 */ modelShow: string; /** * 模型维度 */ modelDimension: number; /** * 请求地址 */ apiHost: string; /** * 密钥 */ apiKey: string; /** * 备注 */ remark: string; } export interface ModelForm extends BaseEntity { /** * 主键 */ id?: number | string; /** * 模型分类 */ category?: string; /** * 模型名称 */ modelName?: string; /** * 模型供应商 */ providerCode?: number | string; /** * 模型描述 */ modelDescribe?: string; /** * 是否显示 */ modelShow?: string; /** * 模型维度 */ modelDimension?: number; /** * 请求地址 */ apiHost?: string; /** * 密钥 */ apiKey?: string; /** * 备注 */ remark?: string; } export interface ModelQuery extends PageQuery { /** * 模型分类 */ category?: string; /** * 模型名称 */ modelName?: string; /** * 模型供应商 */ providerCode?: number | string; /** * 模型描述 */ modelDescribe?: string; /** * 是否显示 */ modelShow?: string; /** * 模型维度 */ modelDimension?: number; /** * 请求地址 */ apiHost?: string; /** * 密钥 */ apiKey?: string; /** * 日期范围参数 */ params?: any; } ================================================ FILE: apps/web-antd/src/api/chat/provider/index.ts ================================================ import type { ProviderForm, ProviderQuery, ProviderVO } from './model'; import type { ID, IDS, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** * 查询厂商管理列表 * @param params * @returns 厂商管理列表 */ export function providerList(params?: ProviderQuery) { return requestClient.get>('/system/provider/list', { params, }); } /** * 导出厂商管理列表 * @param params * @returns 厂商管理列表 */ export function providerExport(params?: ProviderQuery) { return commonExport('/system/provider/export', params ?? {}); } /** * 查询厂商管理详情 * @param id id * @returns 厂商管理详情 */ export function providerInfo(id: ID) { return requestClient.get(`/system/provider/${id}`); } /** * 新增厂商管理 * @param data * @returns void */ export function providerAdd(data: ProviderForm) { return requestClient.postWithMsg('/system/provider', data); } /** * 更新厂商管理 * @param data * @returns void */ export function providerUpdate(data: ProviderForm) { return requestClient.putWithMsg('/system/provider', data); } /** * 删除厂商管理 * @param id id * @returns void */ export function providerRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/system/provider/${id}`); } ================================================ FILE: apps/web-antd/src/api/chat/provider/model.d.ts ================================================ import type { BaseEntity, PageQuery } from '#/api/common'; export interface ProviderVO { /** * 主键 */ id: number | string; /** * 厂商名称 */ providerName: number | string; /** * 厂商编码 */ providerCode: number | string; /** * 厂商图标 */ providerIcon: number | string; /** * 厂商描述 */ providerDesc: number | string; /** * API地址 */ apiHost: string; /** * 状态(0正常 1停用) */ status: string; /** * 排序 */ sortOrder: number; /** * 备注 */ remark: string; /** * 更新IP */ updateIp: string; } export interface ProviderForm extends BaseEntity { /** * 主键 */ id?: number | string; /** * 厂商名称 */ providerName?: number | string; /** * 厂商编码 */ providerCode?: number | string; /** * 厂商图标 */ providerIcon?: number | string; /** * 厂商描述 */ providerDesc?: number | string; /** * API地址 */ apiHost?: string; /** * 状态(0正常 1停用) */ status?: string; /** * 排序 */ sortOrder?: number; /** * 备注 */ remark?: string; /** * 更新IP */ updateIp?: string; } export interface ProviderQuery extends PageQuery { /** * 厂商名称 */ providerName?: number | string; /** * 厂商编码 */ providerCode?: number | string; /** * 厂商图标 */ providerIcon?: number | string; /** * 厂商描述 */ providerDesc?: number | string; /** * API地址 */ apiHost?: string; /** * 状态(0正常 1停用) */ status?: string; /** * 排序 */ sortOrder?: number; /** * 更新IP */ updateIp?: string; /** * 日期范围参数 */ params?: any; } ================================================ FILE: apps/web-antd/src/api/common.d.ts ================================================ export type ID = number | string; export type IDS = (number | string)[]; export interface BaseEntity { createBy?: string; createDept?: string; createTime?: string; updateBy?: string; updateTime?: string; } /** * 分页信息 * @param rows 结果集 * @param total 总数 */ export interface PageResult { rows: T[]; total: number; } /** * 分页查询参数 * * 排序支持的用法如下: * {isAsc:"asc",orderByColumn:"id"} order by id asc * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc * * @param pageNum 当前页 * @param pageSize 每页大小 * @param orderByColumn 排序字段 * @param isAsc 是否升序 */ export interface PageQuery { isAsc?: string; orderByColumn?: string; pageNum?: number; pageSize?: number; [key: string]: any; } ================================================ FILE: apps/web-antd/src/api/core/auth.ts ================================================ import type { GrantType } from '@vben/common-ui'; import type { HttpResponse } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { requestClient } from '#/api/request'; const { clientId, sseEnable } = useAppConfig( import.meta.env, import.meta.env.PROD, ); export namespace AuthApi { /** * @description: 所有登录类型都需要用到的 * @param clientId 客户端ID 这里为必填项 但是在loginApi内部处理了 所以为可选 * @param grantType 授权/登录类型 * @param tenantId 租户id */ export interface BaseLoginParams { clientId?: string; grantType: GrantType; tenantId: string; } /** * @description: oauth登录需要用到的参数 * @param socialCode 第三方参数 * @param socialState 第三方参数 * @param source 与后端的 justauth.type.xxx的回调地址的source对应 */ export interface OAuthLoginParams extends BaseLoginParams { socialCode: string; socialState: string; source: string; } /** * @description: 验证码登录需要用到的参数 * @param code 验证码 可选(未开启验证码情况) * @param uuid 验证码ID 可选(未开启验证码情况) * @param username 用户名 * @param password 密码 */ export interface SimpleLoginParams extends BaseLoginParams { code?: string; uuid?: string; username: string; password: string; } export type LoginParams = OAuthLoginParams | SimpleLoginParams; // /** 登录接口参数 */ // export interface LoginParams { // code?: string; // grantType: string; // password: string; // tenantId: string; // username: string; // uuid?: string; // } /** 登录接口返回值 */ export interface LoginResult { access_token: string; client_id: string; expire_in: number; } export interface RefreshTokenResult { data: string; status: number; } } /** * 登录 */ export async function loginApi(data: AuthApi.LoginParams) { return requestClient.post( '/auth/login', { ...data, clientId }, { encrypt: true, }, ); } /** * 用户登出 * @returns void */ export function doLogout() { return requestClient.post>('/auth/logout'); } /** * 关闭sse连接 * @returns void */ export function seeConnectionClose() { /** * 未开启sse 不需要处理 */ if (!sseEnable) { return; } return requestClient.get('/resource/sse/close'); } /** * @param companyName 租户/公司名称 * @param domain 绑定域名(不带http(s)://) 可选 * @param tenantId 租户id */ export interface TenantOption { companyName: string; domain?: string; tenantId: string; } /** * @param tenantEnabled 是否启用租户 * @param voList 租户列表 */ export interface TenantResp { tenantEnabled: boolean; voList: TenantOption[]; } /** * 获取租户列表 下拉框使用 */ export function tenantList() { return requestClient.get('/auth/tenant/list'); } /** * vben的 先不删除 * @returns string[] */ export async function getAccessCodesApi() { return requestClient.get('/auth/codes'); } /** * 绑定第三方账号 * @param source 绑定的来源 * @returns 跳转url */ export function authBinding(source: string, tenantId: string) { return requestClient.get(`/auth/binding/${source}`, { params: { domain: window.location.host, tenantId, }, }); } /** * 取消绑定 * @param id id */ export function authUnbinding(id: string) { return requestClient.deleteWithMsg(`/auth/unlock/${id}`); } /** * oauth授权回调 * @param data oauth授权 * @returns void */ export function authCallback(data: AuthApi.OAuthLoginParams) { return requestClient.post('/auth/social/callback', data); } ================================================ FILE: apps/web-antd/src/api/core/captcha.ts ================================================ import { requestClient } from '#/api/request'; /** * 发送短信验证码 * @param phonenumber 手机号 * @returns void */ export function sendSmsCode(phonenumber: string) { return requestClient.get('/resource/sms/code', { params: { phonenumber }, }); } /** * 发送邮件验证码 * @param email 邮箱 * @returns void */ export function sendEmailCode(email: string) { return requestClient.get('/resource/email/code', { params: { email }, }); } /** * @param img 图片验证码 需要和base64拼接 * @param captchaEnabled 是否开启 * @param uuid 验证码ID */ export interface CaptchaResponse { captchaEnabled: boolean; img: string; uuid: string; } /** * 图片验证码 * @returns resp */ export function captchaImage() { return requestClient.get('/auth/code'); } ================================================ FILE: apps/web-antd/src/api/core/index.ts ================================================ export * from './auth'; export * from './menu'; export * from './upload'; export * from './user'; ================================================ FILE: apps/web-antd/src/api/core/menu.ts ================================================ import { requestClient } from '#/api/request'; /** * @description: 菜单meta * @param title 菜单名 * @param icon 菜单图标 * @param noCache 是否不缓存 * @param link 外链链接 */ export interface MenuMeta { icon: string; link?: string; noCache: boolean; title: string; } /** * @description: 菜单 * @param name 菜单名 * @param path 菜单路径 * @param hidden 是否隐藏 * @param component 组件名称 Layout * @param alwaysShow 总是显示 * @param query 路由参数(json形式) * @param meta 路由信息 * @param children 子路由信息 */ export interface Menu { alwaysShow?: boolean; children: Menu[]; component: string; hidden: boolean; meta: MenuMeta; name: string; path: string; query?: string; redirect?: string; } /** * 获取用户所有菜单 */ export async function getAllMenusApi() { return requestClient.get('/system/menu/getRouters'); } ================================================ FILE: apps/web-antd/src/api/core/upload.ts ================================================ import type { AxiosRequestConfig } from '@vben/request'; import { requestClient } from '#/api/request'; /** * Axios上传进度事件 */ export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress']; /** * 默认上传结果 */ export interface UploadResult { url: string; fileName: string; ossId: string; } /** * 通过单文件上传接口 * @param file 上传的文件 * @param options 一些配置项 * @param options.onUploadProgress 上传进度事件 * @param options.signal 上传取消信号 * @param options.otherData 其他请求参数 后端拓展可能会用到 * @returns 上传结果 */ export function uploadApi( file: Blob | File, options?: { onUploadProgress?: AxiosProgressEvent; otherData?: Record; signal?: AbortSignal; }, ) { const { onUploadProgress, signal, otherData = {} } = options ?? {}; return requestClient.upload( '/resource/oss/upload', { file, ...otherData }, { onUploadProgress, signal, timeout: 60_000 }, ); } /** * 上传api type */ export type UploadApi = typeof uploadApi; ================================================ FILE: apps/web-antd/src/api/core/user.ts ================================================ import { requestClient } from '#/api/request'; export interface Role { dataScope: string; flag: boolean; roleId: number; roleKey: string; roleName: string; roleSort: number; status: string; superAdmin: boolean; } export interface User { avatar: string; createTime: string; deptId: number; deptName: string; email: string; loginDate: string; loginIp: string; nickName: string; phonenumber: string; remark: string; roles: Role[]; sex: string; status: string; tenantId: string; userId: number; userName: string; userType: string; } export interface UserInfoResp { permissions: string[]; roles: string[]; user: User; } /** * 获取用户信息 * 存在返回null的情况(401) 不会抛出异常 需要手动抛异常 */ export async function getUserInfoApi() { return requestClient.get('/system/user/getInfo'); } ================================================ FILE: apps/web-antd/src/api/graph/index.ts ================================================ import { requestClient } from '#/api/request'; import type { ExtractParams, GraphData, GraphInstance, GraphStats, IngestParams, NeighborQueryParams, PathQueryParams, RetrieveParams, SearchParams, ExtractionResult, GraphRetrievalResult, GraphPath, GraphNode, GraphBuildTask, } from './model'; /** * 知识图谱API接口 */ // ==================== 图谱实例管理 ==================== /** * 获取图谱实例列表 */ export function graphInstanceList(params?: any) { return requestClient.get('/graph/instance/list', { params }); } /** * 创建图谱实例 */ export function graphInstanceAdd(data: Partial) { return requestClient.post('/graph/instance', data); } /** * 更新图谱实例 */ export function graphInstanceUpdate(data: Partial) { return requestClient.put('/graph/instance', data); } /** * 删除图谱实例 */ export function graphInstanceRemove(id: string | string[]) { if (Array.isArray(id)) { return requestClient.delete('/graph/instance/batch', { data: { ids: id } }); } return requestClient.delete(`/graph/instance/${id}`); } /** * 获取图谱实例详情 */ export function graphInstanceInfo(id: string) { return requestClient.get(`/graph/instance/${id}`); } /** * 构建图谱 */ export function graphInstanceBuild(id: string) { return requestClient.post(`/graph/instance/build/${id}`); } /** * 重建图谱 */ export function graphInstanceRebuild(id: string) { return requestClient.post(`/graph/instance/rebuild/${id}`); } /** * 获取构建状态 */ export function graphInstanceStatus(id: string) { return requestClient.get(`/graph/instance/status/${id}`); } /** * 导出图谱实例数据 */ export function graphInstanceExport(params?: any) { return requestClient.post('/graph/instance/export', params, { responseType: 'blob', }); } // ==================== 图谱查询 ==================== /** * 获取知识库的图谱数据 */ export function graphQueryByKnowledge(knowledgeId: string, limit?: number) { return requestClient.get(`/graph/query/knowledge/${knowledgeId}`, { params: { limit }, }); } /** * 搜索实体 */ export function graphSearchEntity(params: SearchParams) { return requestClient.get('/graph/query/search/entity', { params }); // ⭐ 修复路径 } /** * 获取邻居节点 */ export function graphGetNeighbors(params: NeighborQueryParams) { return requestClient.get('/graph/query/neighbors', { params }); } /** * 查找路径 */ export function graphFindPath(params: PathQueryParams) { return requestClient.get('/graph/query/path', { params }); } /** * 获取图谱统计信息 */ export function graphGetStats(knowledgeId: string) { return requestClient.get(`/graph/query/stats/${knowledgeId}`); } /** * 删除图谱数据 */ export function graphDeleteData(knowledgeId: string) { return requestClient.delete(`/graph/query/delete/${knowledgeId}`); } // ==================== 图谱RAG ==================== /** * 实体抽取 */ export function graphExtractEntities(data: ExtractParams) { return requestClient.post('/graph/query/extract', data); } /** * 文本入库 */ export function graphIngestText(data: IngestParams) { return requestClient.post('/graph/query/ingest', data); } /** * 图谱检索 */ export function graphRetrieve(data: RetrieveParams) { return requestClient.post('/graph/query/retrieve', data); } ================================================ FILE: apps/web-antd/src/api/graph/model.d.ts ================================================ /** * 知识图谱相关类型定义 */ // 图谱实例 export interface GraphInstance { id?: string; instanceName: string; knowledgeId: string; knowledgeName?: string; modelName?: string; status?: string; // NOT_BUILT, BUILDING, COMPLETED, FAILED nodeCount?: number; edgeCount?: number; entityTypes?: string; relationTypes?: string; createTime?: string; updateTime?: string; remark?: string; } // 图谱构建任务 export interface GraphBuildTask { id?: string; instanceId?: string; taskType?: string; // FULL_BUILD, INCREMENTAL_BUILD, DOCUMENT_BUILD status?: string; // PENDING, RUNNING, COMPLETED, FAILED, CANCELLED taskStatus?: number; // 任务状态码(后端枚举值) graphStatus?: number; // 图谱状态码(后端枚举值) progress?: number; totalDocuments?: number; processedDocuments?: number; nodeCount?: number; relationshipCount?: number; errorMessage?: string; startTime?: string; endTime?: string; createTime?: string; } // 图谱节点 export interface GraphNode { id?: string; nodeId: string; name: string; label: string; // 实体类型 description?: string; confidence?: number; properties?: string; // JSON字符串 knowledgeId?: string; } // 图谱边 export interface GraphEdge { id?: string; edgeId: string; sourceNodeId: string; targetNodeId: string; label: string; // 关系类型 confidence?: number; weight?: number; properties?: string; // JSON字符串 knowledgeId?: string; } // 图谱数据 export interface GraphData { vertices: GraphNode[]; edges: GraphEdge[]; } // 图谱统计 export interface GraphStats { totalNodes: number; totalEdges: number; entityTypes: Record; relationTypes: Record; } // 实体抽取结果 export interface ExtractedEntity { name: string; type: string; description?: string; } export interface ExtractedRelation { source: string; target: string; type: string; description?: string; } export interface ExtractionResult { entities: ExtractedEntity[]; relations: ExtractedRelation[]; } // 图谱检索结果 export interface GraphRetrievalResult { content: string; relevantEntities: GraphNode[]; relevantRelations: GraphEdge[]; } // 路径查询结果 export interface GraphPath { nodes: GraphNode[]; edges: GraphEdge[]; length: number; } // 邻居查询参数 export interface NeighborQueryParams { nodeId: string; knowledgeId: string; depth?: number; } // 路径查询参数 export interface PathQueryParams { startNodeId: string; endNodeId: string; knowledgeId: string; maxDepth?: number; } // 搜索参数 export interface SearchParams { keyword: string; knowledgeId: string; limit?: number; } // 实体抽取参数 export interface ExtractParams { text: string; modelName?: string; } // 文本入库参数 export interface IngestParams { text: string; knowledgeId: string; modelName?: string; metadata?: Record; } // 检索参数 export interface RetrieveParams { query: string; knowledgeId: string; topK?: number; } // 图谱实例表单 export interface GraphInstanceForm { id?: string; instanceName: string; knowledgeId: string; modelName?: string; entityTypes?: string[]; relationTypes?: string[]; remark?: string; } ================================================ FILE: apps/web-antd/src/api/helper.ts ================================================ import { $t } from '@vben/locales'; import { message, Modal } from 'ant-design-vue'; import { useAuthStore } from '#/store'; import { requestClient } from './request'; /** * @description: contentType */ export const ContentTypeEnum = { // form-data upload FORM_DATA: 'multipart/form-data;charset=UTF-8', // form-data qs FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8', // json JSON: 'application/json;charset=UTF-8', } as const; /** * 通用下载接口 封装一层 * @param url 请求地址 * @param data 请求参数 * @returns blob二进制 */ export function commonExport(url: string, data: Record) { return requestClient.post(url, data, { data, headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, isTransformResponse: false, responseType: 'blob', }); } /** * 定义一个401专用异常 用于可能会用到的区分场景? */ export class UnauthorizedException extends Error {} /** * logout这种接口都返回401 抛出这个异常 */ export class ImpossibleReturn401Exception extends Error {} /** * 是否已经处在登出过程中了 一个标志位 * 主要是防止一个页面会请求多个api 都401 会导致登出执行多次 */ let isLogoutProcessing = false; /** * 防止 调用logout接口 logout又返回401 然后又走到Logout逻辑死循环 */ let lockLogoutRequest = false; /** * 登出逻辑 两个地方用到 提取出来 * @throws UnauthorizedException 抛出特定的异常 */ export function handleUnauthorizedLogout() { const timeoutMsg = $t('http.loginTimeout'); /** * lock 不再请求logout接口 * 这里已经算异常情况了 */ if (lockLogoutRequest) { throw new UnauthorizedException(timeoutMsg); } // 已经在登出过程中 不再执行 if (isLogoutProcessing) { throw new UnauthorizedException(timeoutMsg); } isLogoutProcessing = true; const userStore = useAuthStore(); userStore .logout() .catch((error) => { /** * logout接口返回了401 * 做Lock处理 且 该标志位不会复位(因为这种场景出现 系统已经算故障了) * 因为这已经不符合正常的逻辑了 */ if (error instanceof ImpossibleReturn401Exception) { lockLogoutRequest = true; if (import.meta.env.DEV) { Modal.error({ title: '提示', centered: true, content: '检测到你的logout接口返回了401, 去检查你的后端配置 这已经不符合正常逻辑(该提示不会在非dev环境弹出)', }); } } }) .finally(() => { message.error(timeoutMsg); isLogoutProcessing = false; }); // 不再执行下面逻辑 throw new UnauthorizedException(timeoutMsg); } ================================================ FILE: apps/web-antd/src/api/index.ts ================================================ export * from './core'; ================================================ FILE: apps/web-antd/src/api/knowledge/attach/index.ts ================================================ import type { AttachVO, AttachForm, AttachQuery } from './model'; import type { ID, IDS } from '#/api/common'; import type { PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** * 查询知识库附件列表 * @param params * @returns 知识库附件列表 */ export function attachList(params?: AttachQuery) { return requestClient.get>('/system/attach/list', { params }); } /** * 导出知识库附件列表 * @param params * @returns 知识库附件列表 */ export function attachExport(params?: AttachQuery) { return commonExport('/system/attach/export', params ?? {}); } /** * 查询知识库附件详情 * @param id id * @returns 知识库附件详情 */ export function attachInfo(id: ID) { return requestClient.get(`/system/attach/${id}`); } /** * 新增知识库附件 * @param data * @returns void */ export function attachAdd(data: AttachForm) { return requestClient.postWithMsg('/system/attach', data); } /** * 更新知识库附件 * @param data * @returns void */ export function attachUpdate(data: AttachForm) { return requestClient.putWithMsg('/system/attach', data); } /** * 删除知识库附件 * @param id id * @returns void */ export function attachRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/system/attach/${id}`); } /** * 手动解析附件内容 * @param id id */ export function attachParse(id: ID) { return requestClient.postWithMsg(`/system/attach/parse/${id}`); } ================================================ FILE: apps/web-antd/src/api/knowledge/attach/model.d.ts ================================================ import type { PageQuery, BaseEntity } from '#/api/common'; export interface AttachVO { /** * */ id: string | number; /** * 知识库ID */ knowledgeId: string | number; /** * 附件名称 */ name: string; /** * 附件类型 */ type: string; /** * 对象存储ID */ ossId: string | number; /** * 文档内容 */ content: string; /** * 备注 */ remark: string; /** * 解析状态 */ status: number; } export interface AttachForm extends BaseEntity { /** * */ id?: string | number; /** * 知识库ID */ knowledgeId?: string | number; /** * 附件名称 */ name?: string; /** * 附件类型 */ type?: string; /** * 对象存储ID */ ossId?: string | number; /** * 文档内容 */ content?: string; /** * 备注 */ remark?: string; } export interface AttachQuery extends PageQuery { /** * 知识库ID */ knowledgeId?: string | number; /** * 附件名称 */ name?: string; /** * 附件类型 */ type?: string; /** * 对象存储ID */ ossId?: string | number; /** * 文档内容 */ content?: string; /** * 日期范围参数 */ params?: any; } ================================================ FILE: apps/web-antd/src/api/knowledge/fragment/index.ts ================================================ import type { FragmentVO, FragmentForm, FragmentQuery } from './model'; import type { ID, IDS } from '#/api/common'; import type { PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** * 查询知识片段列表 * @param params * @returns 知识片段列表 */ export function fragmentList(params?: FragmentQuery) { return requestClient.get>('/system/fragment/list', { params }); } /** * 导出知识片段列表 * @param params * @returns 知识片段列表 */ export function fragmentExport(params?: FragmentQuery) { return commonExport('/system/fragment/export', params ?? {}); } /** * 查询知识片段详情 * @param id id * @returns 知识片段详情 */ export function fragmentInfo(id: ID) { return requestClient.get(`/system/fragment/${id}`); } /** * 新增知识片段 * @param data * @returns void */ export function fragmentAdd(data: FragmentForm) { return requestClient.postWithMsg('/system/fragment', data); } /** * 更新知识片段 * @param data * @returns void */ export function fragmentUpdate(data: FragmentForm) { return requestClient.putWithMsg('/system/fragment', data); } /** * 删除知识片段 * @param id id * @returns void */ export function fragmentRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/system/fragment/${id}`); } ================================================ FILE: apps/web-antd/src/api/knowledge/fragment/model.d.ts ================================================ import type { PageQuery, BaseEntity } from '#/api/common'; export interface FragmentVO { /** * */ id: string | number; /** * 附件ID */ attachId: string | number; /** * 文档ID */ docId: string; /** * 片段索引下标 */ idx: string | number; /** * 文档内容 */ content: string; /** * 备注 */ remark: string; } export interface FragmentForm extends BaseEntity { /** * */ id?: string | number; /** * 附件ID */ attachId?: string | number; /** * 文档ID */ docId?: string; /** * 片段索引下标 */ idx?: string | number; /** * 文档内容 */ content?: string; /** * 备注 */ remark?: string; } export interface FragmentQuery extends PageQuery { /** * 附件ID */ attachId?: string | number; /** * 文档ID */ docId?: string; /** * 片段索引下标 */ idx?: string | number; /** * 文档内容 */ content?: string; /** * 日期范围参数 */ params?: any; } ================================================ FILE: apps/web-antd/src/api/knowledge/info/index.ts ================================================ import type { InfoVO, InfoForm, InfoQuery } from './model'; import type { ID, IDS } from '#/api/common'; import type { PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; /** * 查询知识库列表 * @param params * @returns 知识库列表 */ export function infoList(params?: InfoQuery) { return requestClient.get>('/system/info/list', { params }); } /** * 导出知识库列表 * @param params * @returns 知识库列表 */ export function infoExport(params?: InfoQuery) { return commonExport('/system/info/export', params ?? {}); } /** * 查询知识库详情 * @param id id * @returns 知识库详情 */ export function infoInfo(id: ID) { return requestClient.get(`/system/info/${id}`); } /** * 新增知识库 * @param data * @returns void */ export function infoAdd(data: InfoForm) { return requestClient.postWithMsg('/system/info', data); } /** * 更新知识库 * @param data * @returns void */ export function infoUpdate(data: InfoForm) { return requestClient.putWithMsg('/system/info', data); } /** * 删除知识库 * @param id id * @returns void */ export function infoRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/system/info/${id}`); } /** * 检索测试 * @param data * @returns 检索结果 */ export function knowledgeRetrieval(data: any) { return requestClient.post('/system/fragment/retrieval', data); } ================================================ FILE: apps/web-antd/src/api/knowledge/info/model.d.ts ================================================ import type { PageQuery, BaseEntity } from '#/api/common'; export interface InfoVO { /** * 主键 */ id: string | number; /** * 用户ID */ userId: string | number; /** * 知识库名称 */ name: string; /** * 是否公开知识库(0 否 1是) */ share: number; /** * 知识库描述 */ description: string; /** * 知识分隔符 */ separator: string; /** * 重叠字符数 */ overlapChar: number; /** * 知识库中检索的条数 */ retrieveLimit: number; /** * 文本块大小 */ textBlockSize: number; /** * 向量库 */ vectorModel: string; /** * 向量模型 */ embeddingModel: string; /** * 是否启用重排序(0 否 1是) */ enableRerank: number; /** * 重排序模型名称 */ rerankModel: string; /** * 重排序后返回数量 */ rerankTopN: number; /** * 重排序分数阈值(0-1) */ rerankScoreThreshold: number; /** * 是否启用混合检索(0 否 1 是) */ enableHybrid: number; /** * 混合检索权重比例 (0.0-1.0) */ hybridAlpha: number; /** * 备注 */ remark: string; } export interface InfoForm extends BaseEntity { /** * 主键 */ id?: string | number; /** * 用户ID */ userId?: string | number; /** * 知识库名称 */ name?: string; /** * 是否公开知识库(0 否 1是) */ share?: number; /** * 知识库描述 */ description?: string; /** * 知识分隔符 */ separator?: string; /** * 重叠字符数 */ overlapChar?: number; /** * 知识库中检索的条数 */ retrieveLimit?: number; /** * 文本块大小 */ textBlockSize?: number; /** * 向量库 */ vectorModel?: string; /** * 向量模型 */ embeddingModel?: string; /** * 是否启用重排序(0 否 1是) */ enableRerank?: number; /** * 重排序模型名称 */ rerankModel?: string; /** * 重排序后返回数量 */ rerankTopN?: number; /** * 重排序分数阈值(0-1) */ rerankScoreThreshold?: number; /** * 是否启用混合检索(0 否 1 是) */ enableHybrid?: number; /** * 混合检索权重比例 (0.0-1.0) */ hybridAlpha?: number; /** * 备注 */ remark?: string; } export interface InfoQuery extends PageQuery { /** * 用户ID */ userId?: string | number; /** * 知识库名称 */ name?: string; /** * 是否公开知识库(0 否 1是) */ share?: number; /** * 知识库描述 */ description?: string; /** * 知识分隔符 */ separator?: string; /** * 重叠字符数 */ overlapChar?: number; /** * 知识库中检索的条数 */ retrieveLimit?: number; /** * 文本块大小 */ textBlockSize?: number; /** * 向量库 */ vectorModel?: string; /** * 向量模型 */ embeddingModel?: string; /** * 日期范围参数 */ params?: any; } ================================================ FILE: apps/web-antd/src/api/mcp/market/index.ts ================================================ import type { McpMarket, McpMarketRefreshResult, McpMarketTool } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { mcpMarketExport = '/mcp/market/export', mcpMarketList = '/mcp/market/list', mcpMarketAll = '/mcp/market/all', root = '/mcp/market', } /** * 查询MCP市场分页列表 * @param params 请求参数 * @returns 列表 */ export function mcpMarketList(params?: PageQuery) { return requestClient.get>(Api.mcpMarketList, { params }); } /** * 查询所有MCP市场(不分页) * @returns 列表 */ export function mcpMarketAll() { return requestClient.get(Api.mcpMarketAll); } /** * 导出MCP市场excel * @param data 请求参数 */ export function mcpMarketExport(data: Partial) { return commonExport(Api.mcpMarketExport, data); } /** * MCP市场详情 * @param id id * @returns 详情 */ export function mcpMarketInfo(id: ID) { return requestClient.get(`${Api.root}/${id}`); } /** * MCP市场新增 * @param data 参数 */ export function mcpMarketAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * MCP市场修改 * @param data 参数 */ export function mcpMarketUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * MCP市场状态修改 * @param data 状态 */ export function mcpMarketChangeStatus(data: any) { return requestClient.putWithMsg( `${Api.root}/${data.id}/status?status=${data.status}`, ); } /** * MCP市场删除 * @param ids id集合 */ export function mcpMarketRemove(ids: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${ids}`); } /** * 刷新市场工具列表 * @param marketId 市场ID */ export function mcpMarketRefresh(marketId: ID) { return requestClient.postWithMsg(`${Api.root}/${marketId}/refresh`); } /** * 加载单个工具到本地 * @param toolId 工具ID */ export function mcpMarketLoadTool(toolId: ID) { return requestClient.postWithMsg(`${Api.root}/tools/${toolId}/load`); } /** * 批量加载工具到本地 * @param toolIds 工具ID列表 */ export function mcpMarketBatchLoadTools(toolIds: ID[]) { return requestClient.postWithMsg(`${Api.root}/tools/batch-load`, toolIds); } /** * 获取市场工具列表 * @param marketId 市场ID */ export function mcpMarketToolList(marketId: ID) { return requestClient.get(`${Api.root}/${marketId}/tools`); } ================================================ FILE: apps/web-antd/src/api/mcp/market/model.d.ts ================================================ export interface McpMarket { id: number; name: string; url: string; description: string; authConfig: string; status: string; createTime: string; updateTime: string; } export interface McpMarketTool { id: number; marketId: number; toolName: string; toolDescription: string; toolVersion: string; toolMetadata: string; isLoaded: boolean; localToolId: number; } export interface McpMarketRefreshResult { success: boolean; message: string; addedCount: number; updatedCount: number; } ================================================ FILE: apps/web-antd/src/api/mcp/tool/index.ts ================================================ import type { McpTool, McpToolTestResult } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { mcpToolTest = '/mcp/tool', mcpToolExport = '/mcp/tool/export', mcpToolList = '/mcp/tool/list', mcpToolAll = '/mcp/tool/all', root = '/mcp/tool', } /** * 查询MCP工具分页列表 * @param params 请求参数 * @returns 列表 */ export function mcpToolList(params?: PageQuery) { return requestClient.get>(Api.mcpToolList, { params }); } /** * 查询所有MCP工具(不分页) * @returns 列表 */ export function mcpToolAll() { return requestClient.get(Api.mcpToolAll); } /** * 导出MCP工具excel * @param data 请求参数 */ export function mcpToolExport(data: Partial) { return commonExport(Api.mcpToolExport, data); } /** * MCP工具详情 * @param id id * @returns 详情 */ export function mcpToolInfo(id: ID) { return requestClient.get(`${Api.root}/${id}`); } /** * MCP工具新增 * @param data 参数 */ export function mcpToolAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * MCP工具修改 * @param data 参数 */ export function mcpToolUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * MCP工具状态修改 * @param data 状态 */ export function mcpToolChangeStatus(data: any) { return requestClient.putWithMsg( `${Api.root}/${data.id}/status?status=${data.status}`, ); } /** * MCP工具删除 * @param ids id集合 */ export function mcpToolRemove(ids: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${ids}`); } /** * MCP工具测试 * @param id 工具ID */ export function mcpToolTest(id: ID) { return requestClient.post(`${Api.root}/${id}/test`); } ================================================ FILE: apps/web-antd/src/api/mcp/tool/model.d.ts ================================================ export interface McpTool { id: number; name: string; description: string; type: string; status: string; configJson: string; createTime: string; updateTime: string; } export interface McpToolTestResult { success: boolean; message: string; data?: any; } ================================================ FILE: apps/web-antd/src/api/monitor/cache/index.ts ================================================ import { requestClient } from '#/api/request'; export interface CommandStats { name: string; value: string; } export interface RedisInfo { [key: string]: string; } export interface CacheInfo { commandStats: CommandStats[]; dbSize: number; info: RedisInfo; } /** * * @returns redis信息 */ export function redisCacheInfo() { return requestClient.get('/monitor/cache'); } ================================================ FILE: apps/web-antd/src/api/monitor/logininfo/index.ts ================================================ import type { LoginLog } from './model'; import type { IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { loginInfoClean = '/monitor/logininfor/clean', loginInfoExport = '/monitor/logininfor/export', loginInfoList = '/monitor/logininfor/list', root = '/monitor/logininfor', userUnlock = '/monitor/logininfor/unlock', } /** * 登录日志列表 * @param params 查询参数 * @returns list[] */ export function loginInfoList(params?: PageQuery) { return requestClient.get>(Api.loginInfoList, { params }); } /** * 导出登录日志 * @param data 表单参数 * @returns excel */ export function loginInfoExport(data: any) { return commonExport(Api.loginInfoExport, data); } /** * 移除登录日志 * @param infoIds 登录日志id数组 * @returns void */ export function loginInfoRemove(infoIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${infoIds}`); } /** * 账号解锁 * @param username 用户名(账号) * @returns void */ export function userUnlock(username: string) { return requestClient.get(`${Api.userUnlock}/${username}`, { successMessageMode: 'message', }); } /** * 清空全部登录日志 * @returns void */ export function loginInfoClean() { return requestClient.deleteWithMsg(Api.loginInfoClean); } ================================================ FILE: apps/web-antd/src/api/monitor/logininfo/model.d.ts ================================================ export interface LoginLog { infoId: string; tenantId: string; userName: string; status: string; ipaddr: string; loginLocation: string; browser: string; os: string; msg: string; loginTime: string; clientKey: string; } ================================================ FILE: apps/web-antd/src/api/monitor/online/index.ts ================================================ import type { OnlineUser } from './model'; import type { PageQuery, PageResult } from '#/api/common'; import { requestClient } from '#/api/request'; enum Api { onlineList = '/monitor/online/list', root = '/monitor/online', } /** * 当前账号的在线设备 个人中心使用 * @returns OnlineUser[] */ export function onlineDeviceList() { return requestClient.get>(Api.root); } /** * 这里的分页参数无效 返回的是全部的分页 * @param params 请求参数 * @returns 结果 */ export function onlineList(params?: PageQuery) { return requestClient.get>(Api.onlineList, { params }); } /** * 强制下线 * @param tokenId 用户token * @returns void */ export function forceLogout(tokenId: string) { return requestClient.deleteWithMsg(`${Api.root}/${tokenId}`); } /** * 个人中心用的 跟上面的不同是用的Post * @param tokenId 用户token * @returns void */ export function forceLogout2(tokenId: string) { return requestClient.deleteWithMsg(`${Api.root}/myself/${tokenId}`); } ================================================ FILE: apps/web-antd/src/api/monitor/online/model.d.ts ================================================ export interface OnlineUser { tokenId: string; deptName: string; userName: string; ipaddr: string; loginLocation: string; browser: string; os: string; loginTime: number; deviceType: string; } ================================================ FILE: apps/web-antd/src/api/monitor/operlog/index.ts ================================================ import type { OperationLog } from './model'; import type { IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { operLogClean = '/monitor/operlog/clean', operLogExport = '/monitor/operlog/export', operLogList = '/monitor/operlog/list', root = '/monitor/operlog', } /** * 操作日志分页 * @param params 查询参数 * @returns 分页结果 */ export function operLogList(params?: PageQuery) { return requestClient.get>(Api.operLogList, { params, }); } /** * 删除操作日志 * @param operIds id/ids */ export function operLogDelete(operIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${operIds}`); } /** * 清空全部分页日志 */ export function operLogClean() { return requestClient.deleteWithMsg(Api.operLogClean); } /** * 导出操作日志 * @param data 查询参数 */ export function operLogExport(data: Partial) { return commonExport(Api.operLogExport, data); } ================================================ FILE: apps/web-antd/src/api/monitor/operlog/model.d.ts ================================================ export interface OperationLog { operId: string; tenantId: string; title: string; businessType: string; businessTypes?: any; method: string; requestMethod: string; operatorType: number; operName: string; deptName: string; operUrl: string; operIp: string; operLocation: string; operParam: string; jsonResult: string; status: string; errorMsg: string; operTime: string; costTime: number; } ================================================ FILE: apps/web-antd/src/api/operator/configurationManage/index.ts ================================================ import { requestClient } from '#/api/request'; enum Api { addConfig = '/chat/config/saveOrUpdate', listConfig = '/chat/config/list', } export function listConfig() { return requestClient.get(Api.listConfig); } export function addConfig(data: any) { return requestClient.post(Api.addConfig, data); } ================================================ FILE: apps/web-antd/src/api/request.ts ================================================ /** * 该文件可自行根据业务逻辑进行调整 */ import type { HttpResponse } from '@vben/request'; import type { BaseAsymmetricEncryption, BaseSymmetricEncryption, } from '@vben/utils'; import { BUSINESS_SUCCESS_CODE, UNAUTHORIZED_CODE } from '@vben/constants'; import { useAppConfig } from '@vben/hooks'; import { $t } from '@vben/locales'; import { preferences } from '@vben/preferences'; import { authenticateResponseInterceptor, errorMessageResponseInterceptor, RequestClient, stringify, } from '@vben/request'; import { useAccessStore } from '@vben/stores'; import { AesEncryption, decodeBase64, encodeBase64, randomStr, RsaEncryption, } from '@vben/utils'; import { message, Modal } from 'ant-design-vue'; import { isEmpty, isNull } from 'lodash-es'; import { useAuthStore } from '#/store'; import { handleUnauthorizedLogout } from './helper'; const { apiURL, clientId, enableEncrypt, rsaPublicKey, rsaPrivateKey } = useAppConfig(import.meta.env, import.meta.env.PROD); /** * 使用非对称加密的实现 前端已经实现RSA/SM2 * * 你可以使用Sm2Encryption来替换 后端也需要同步替换公私钥对 * * 后端文件位置: ruoyi-common/ruoyi-common-encrypt/src/main/java/org/dromara/common/encrypt/filter/DecryptRequestBodyWrapper.java * * 注意前端sm-crypto库只能支持04开头的公钥! 否则加密会有问题 你可以使用前端的import { logSm2KeyPair } from '@vben/utils';方法来生成 * 如果你生成的公钥开头不是04 那么不能正常加密 * 或者使用这个网站来生成: https://tool.hiofd.com/sm2-key-gen/ */ const asymmetricEncryption: BaseAsymmetricEncryption = new RsaEncryption({ publicKey: rsaPublicKey, privateKey: rsaPrivateKey, }); /** * 对称加密的实现 AES/SM4 */ const symmetricEncryption: BaseSymmetricEncryption = new AesEncryption(); function createRequestClient(baseURL: string) { const client = new RequestClient({ // 后端地址 baseURL, // 消息提示类型 errorMessageMode: 'message', // 是否返回原生响应 比如:需要获取响应头时使用该属性 isReturnNativeResponse: false, // 需要对返回数据进行处理 isTransformResponse: true, }); /** * 重新认证逻辑 */ async function doReAuthenticate() { console.warn('Access token or refresh token is invalid or expired. '); const accessStore = useAccessStore(); const authStore = useAuthStore(); accessStore.setAccessToken(null); if ( preferences.app.loginExpiredMode === 'modal' && accessStore.isAccessChecked ) { accessStore.setLoginExpired(true); } else { await authStore.logout(); } } /** * 刷新token逻辑 */ async function doRefreshToken() { // 不需要 // 保留此方法只是为了合并方便 return ''; } function formatToken(token: null | string) { return token ? `Bearer ${token}` : null; } client.addRequestInterceptor({ fulfilled: (config) => { const accessStore = useAccessStore(); // 添加token config.headers.Authorization = formatToken(accessStore.accessToken); const isFileUpload = config.data instanceof FormData || config.data instanceof File || config.data instanceof Blob; if (isFileUpload) { delete config.headers['Content-Type']; } /** * locale跟后台不一致 需要转换 */ const language = preferences.app.locale.replace('-', '_'); config.headers['Accept-Language'] = language; config.headers['Content-Language'] = language; /** * 添加全局clientId * 关于header的clientId被错误绑定到实体类 * https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS */ config.headers.ClientID = clientId; /** * 格式化get/delete参数 * 如果包含自定义的paramsSerializer则不走此逻辑 */ if ( ['DELETE', 'GET'].includes(config.method?.toUpperCase() || '') && config.params && !config.paramsSerializer ) { /** * 1. 格式化参数 微服务在传递区间时间选择(后端的params Map类型参数)需要格式化key 否则接收不到 * 2. 数组参数需要格式化 后端才能正常接收 会变成arr=1&arr=2&arr=3的格式来接收 */ config.paramsSerializer = (params) => stringify(params, { arrayFormat: 'repeat' }); } const { encrypt } = config; // 全局开启请求加密功能 && 该请求开启 && 是post/put请求 if ( !isFileUpload && enableEncrypt && encrypt && ['POST', 'PUT'].includes(config.method?.toUpperCase() || '') ) { // sm4这里改为randomStr(16) const key = randomStr(32); const keyWithBase64 = encodeBase64(key); config.headers['encrypt-key'] = asymmetricEncryption.encrypt(keyWithBase64); /** * axios会默认给字符串前后加上引号 RSA可以正常解密(加不加都能解密) 但是SM2不行(大坑!!!) * 这里通过transformRequest强制返回原始内容 */ config.transformRequest = (data) => data; config.data = typeof config.data === 'object' ? symmetricEncryption.encrypt(JSON.stringify(config.data), key) : symmetricEncryption.encrypt(config.data, key); } return config; }, }); // 通用的错误处理, 如果没有进入上面的错误处理逻辑,就会进入这里 // 主要处理http状态码不为200(如网络异常/离线)的情况 必须放在在下面的响应拦截器之前 client.addResponseInterceptor( errorMessageResponseInterceptor((msg: string) => message.error(msg)), ); client.addResponseInterceptor({ fulfilled: async (response) => { const encryptKey = (response.headers ?? {})['encrypt-key']; if (encryptKey) { /** RSA私钥解密 拿到解密秘钥的base64 */ const base64Str = asymmetricEncryption.decrypt(encryptKey); /** base64 解码 得到请求头的 AES 秘钥 */ const secret = decodeBase64(base64Str); /** 使用aesKey解密 responseData */ const decryptData = symmetricEncryption.decrypt( response.data as unknown as string, secret, ); /** 赋值 需要转为对象 */ response.data = JSON.parse(decryptData); } const { isReturnNativeResponse, isTransformResponse } = response.config; // 是否返回原生响应 比如:需要获取响应时使用该属性 if (isReturnNativeResponse) { return response; } // 不进行任何处理,直接返回 // 用于页面代码可能需要直接获取code,data,message这些信息时开启 if (!isTransformResponse) { /** * @warning 注意 微服务版本在401(网关)会返回text/plain的头 所以这里代码会无效 * 我建议你改后端而不是前端来做兼容 */ // json数据的判断 if (response.headers['content-type']?.includes?.('application/json')) { /** * 需要判断是否登录超时/401 * 执行登出操作 */ const resp = response.data as unknown as HttpResponse; // 抛出异常 不再执行 if ( typeof resp === 'object' && Reflect.has(resp, 'code') && resp.code === UNAUTHORIZED_CODE ) { handleUnauthorizedLogout(); } /** * 需要判断下载二进制的情况 正常是返回二进制 报错会返回json * 当type为blob且content-type为application/json时 则判断已经下载出错 */ if (response.config.responseType === 'blob') { // 这时候的data为blob类型 const blob = response.data as unknown as Blob; // 拿到字符串转json对象 response.data = JSON.parse(await blob.text()); // 然后按正常逻辑执行下面的代码(判断业务状态码) } else { // 其他类型数据 直接返回 return response.data; } } else { // 非json数据 直接返回 不做校验 return response.data; } } const axiosResponseData = response.data; if (!axiosResponseData) { throw new Error($t('http.apiRequestFailed')); } // 后端并没有采用严格的{code, msg, data}模式 const { code, data, msg, ...other } = axiosResponseData; // 业务状态码为200 则请求成功 const hasSuccess = Reflect.has(axiosResponseData, 'code') && code === BUSINESS_SUCCESS_CODE; if (hasSuccess) { let successMsg = msg; if (isNull(successMsg) || isEmpty(successMsg)) { successMsg = $t(`http.operationSuccess`); } if (response.config.successMessageMode === 'modal') { Modal.success({ content: successMsg, title: $t('http.successTip'), }); } else if (response.config.successMessageMode === 'message') { message.success(successMsg); } // 分页情况下为code msg rows total 并没有data字段 // 如果有data 直接返回data 没有data将剩余参数(...other)封装为data返回 // 需要考虑data为null的情况(比如查询为空) 所以这里直接判断undefined if (data !== undefined) { return data; } // 没有data 将其他参数包装为data return other; } // 在此处根据自己项目的实际情况对不同的code执行不同的操作 // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可 let timeoutMsg = ''; switch (code) { // 登录超时 case UNAUTHORIZED_CODE: { handleUnauthorizedLogout(); break; } default: { if (msg) { timeoutMsg = msg; } } } // errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误 // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示 if (response.config.errorMessageMode === 'modal') { Modal.error({ content: timeoutMsg, title: $t('http.errorTip'), }); } else if (response.config.errorMessageMode === 'message') { message.error(timeoutMsg); } throw new Error(timeoutMsg || $t('http.apiRequestFailed')); }, }); // token过期的处理 client.addResponseInterceptor( authenticateResponseInterceptor({ client, doReAuthenticate, doRefreshToken, enableRefreshToken: preferences.app.enableRefreshToken, formatToken, }), ); return client; } export const requestClient = createRequestClient(apiURL); export const baseRequestClient = new RequestClient({ baseURL: apiURL }); ================================================ FILE: apps/web-antd/src/api/system/client/index.ts ================================================ import type { Client } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { clientChangeStatus = '/system/client/changeStatus', clientExport = '/system/client/export', clientList = '/system/client/list', root = '/system/client', } /** * 查询客户端分页列表 * @param params 请求参数 * @returns 列表 */ export function clientList(params?: PageQuery) { return requestClient.get>(Api.clientList, { params }); } /** * 导出客户端excel * @param data 请求参数 */ export function clientExport(data: Partial) { return commonExport(Api.clientExport, data); } /** * 客户端详情 * @param id id * @returns 详情 */ export function clientInfo(id: ID) { return requestClient.get(`${Api.root}/${id}`); } /** * 客户端新增 * @param data 参数 */ export function clientAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 客户端修改 * @param data 参数 */ export function clientUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 客户端状态修改 * @param data 状态 */ export function clientChangeStatus(data: any) { const requestData = { clientId: data.clientId, status: data.status, }; return requestClient.putWithMsg(Api.clientChangeStatus, requestData); } /** * 客户端删除 * @param ids id集合 */ export function clientRemove(ids: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${ids}`); } ================================================ FILE: apps/web-antd/src/api/system/client/model.d.ts ================================================ export interface Client { id: number; clientId: string; clientKey: string; clientSecret: string; grantTypeList: string[]; grantType: string; deviceType: string; activeTimeout: number; timeout: number; status: string; } ================================================ FILE: apps/web-antd/src/api/system/config/index.ts ================================================ import type { SysConfig } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { configExport = '/system/config/export', configInfoByKey = '/system/config/configKey', configList = '/system/config/list', configRefreshCache = '/system/config/refreshCache', root = '/system/config', } /** * 系统参数分页列表 * @param params 请求参数 * @returns 列表 */ export function configList(params?: PageQuery) { return requestClient.get>(Api.configList, { params }); } export function configInfo(configId: ID) { return requestClient.get(`${Api.root}/${configId}`); } /** * 导出 * @param data 参数 */ export function configExport(data: Partial) { return commonExport(Api.configExport, data); } /** * 刷新缓存 * @returns void */ export function configRefreshCache() { return requestClient.deleteWithMsg(Api.configRefreshCache); } /** * 更新系统配置 * @param data 参数 */ export function configUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 新增系统配置 * @param data 参数 */ export function configAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 删除配置 * @param configIds ids */ export function configRemove(configIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${configIds}`); } /** * 获取配置信息 * @param configKey configKey * @returns value */ export function configInfoByKey(configKey: string) { return requestClient.get(`${Api.configInfoByKey}/${configKey}`); } ================================================ FILE: apps/web-antd/src/api/system/config/model.d.ts ================================================ export interface SysConfig { configId: number; configName: string; configKey: string; configValue: string; configType: string; remark: string; createTime: string; } ================================================ FILE: apps/web-antd/src/api/system/dept/index.ts ================================================ import type { Dept } from './model'; import type { ID } from '#/api/common'; import { requestClient } from '#/api/request'; enum Api { deptList = '/system/dept/list', deptNodeInfo = '/system/dept/list/exclude', root = '/system/dept', } /** * 部门列表 * @returns list */ export function deptList(params?: { deptName?: string; status?: string }) { return requestClient.get(Api.deptList, { params }); } /** * 查询部门列表(排除节点) * @param deptId 部门ID * @returns void */ export function deptNodeList(deptId: ID) { return requestClient.get(`${Api.deptNodeInfo}/${deptId}`); } /** * 部门详情 * @param deptId 部门id * @returns 部门信息 */ export function deptInfo(deptId: ID) { return requestClient.get(`${Api.root}/${deptId}`); } /** * 部门新增 * @param data 参数 */ export function deptAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 部门更新 * @param data 参数 */ export function deptUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 注意这里只允许单删除 * @param deptId ID * @returns void */ export function deptRemove(deptId: ID) { return requestClient.deleteWithMsg(`${Api.root}/${deptId}`); } ================================================ FILE: apps/web-antd/src/api/system/dept/model.d.ts ================================================ export interface Dept { createBy: string; createTime: string; updateBy?: string; updateTime?: string; remark?: string; deptId: number; parentId: number; ancestors: string; deptName: string; orderNum: number; leader: string; phone: string; email: string; status: string; delFlag: string; parentName?: string; children?: Dept[]; } ================================================ FILE: apps/web-antd/src/api/system/dict/dict-data-model.d.ts ================================================ export interface DictData { createBy: string; createTime: string; cssClass: string; default: boolean; dictCode: number; dictLabel: string; dictSort: number; dictType: string; dictValue: string; isDefault: string; listClass: string; remark: string; status: string; updateBy?: any; updateTime?: any; } ================================================ FILE: apps/web-antd/src/api/system/dict/dict-data.ts ================================================ import type { DictData } from './dict-data-model'; import type { ID, IDS, PageQuery } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { dictDataExport = '/system/dict/data/export', dictDataList = '/system/dict/data/list', root = '/system/dict/data', } /** * 主要是DictTag组件使用 * @param dictType 字典类型 * @returns 字典数据 */ export function dictDataInfo(dictType: string) { return requestClient.get(`${Api.root}/type/${dictType}`); } /** * 字典数据 * @param params 查询参数 * @returns 字典数据列表 */ export function dictDataList(params?: PageQuery) { return requestClient.get(Api.dictDataList, { params }); } /** * 导出字典数据 * @param data 表单参数 * @returns blob */ export function dictDataExport(data: Partial) { return commonExport(Api.dictDataExport, data); } /** * 删除 * @param dictIds 字典ID Array * @returns void */ export function dictDataRemove(dictIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${dictIds}`); } /** * 新增 * @param data 表单参数 * @returns void */ export function dictDataAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 修改 * @param data 表单参数 * @returns void */ export function dictDataUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 查询字典数据详细 * @param dictCode 字典编码 * @returns 字典数据 */ export function dictDetailInfo(dictCode: ID) { return requestClient.get(`${Api.root}/${dictCode}`); } ================================================ FILE: apps/web-antd/src/api/system/dict/dict-type-model.d.ts ================================================ export interface DictType { createTime: string; dictId: number; dictName: string; dictType: string; remark: string; status: string; } ================================================ FILE: apps/web-antd/src/api/system/dict/dict-type.ts ================================================ import type { DictType } from './dict-type-model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { dictOptionSelectList = '/system/dict/type/optionselect', dictTypeExport = '/system/dict/type/export', dictTypeList = '/system/dict/type/list', dictTypeRefreshCache = '/system/dict/type/refreshCache', root = '/system/dict/type', } /** * 获取字典类型列表 * @param params 请求参数 * @returns list */ export function dictTypeList(params?: PageQuery) { return requestClient.get>(Api.dictTypeList, { params }); } /** * 导出字典类型列表 * @param data 表单参数 * @returns blob */ export function dictTypeExport(data: Partial) { return commonExport(Api.dictTypeExport, data); } /** * 删除字典类型 * @param dictIds 字典类型id数组 * @returns void */ export function dictTypeRemove(dictIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${dictIds}`); } /** * 刷新字典缓存 * @returns void */ export function refreshDictTypeCache() { return requestClient.deleteWithMsg(Api.dictTypeRefreshCache); } /** * 新增 * @param data 表单参数 * @returns void */ export function dictTypeAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 修改 * @param data 表单参数 * @returns void */ export function dictTypeUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 查询详情 * @param dictId 字典类型id * @returns 信息 */ export function dictTypeInfo(dictId: ID) { return requestClient.get(`${Api.root}/${dictId}`); } /** * 这个在ele用到 v5用不上 * 下拉框 返回值和list一样 * @returns options */ export function dictOptionSelectList() { return requestClient.get(Api.dictOptionSelectList); } ================================================ FILE: apps/web-antd/src/api/system/dict/index.ts ================================================ import type { DictData } from './dict-data-model'; import type { DictType } from './dict-type-model'; import { dictDataInfo } from './dict-data'; export type { DictData, DictType }; export { dictDataInfo, dictDataList, dictDataExport, dictDataRemove, dictDataAdd, dictDataUpdate, dictDetailInfo, } from './dict-data'; export { dictTypeList, dictTypeExport, dictTypeRemove, refreshDictTypeCache, dictTypeAdd, dictTypeUpdate, dictTypeInfo, dictOptionSelectList, } from './dict-type'; /** * 获取字典数据项 (dictDataInfo的别名) * @param dictType 字典类型 * @returns 字典数据 */ export function getDictItems(dictType: string) { return dictDataInfo(dictType); } ================================================ FILE: apps/web-antd/src/api/system/menu/index.ts ================================================ import type { Menu, MenuOption, MenuQuery, MenuResp } from './model'; import type { ID, IDS } from '#/api/common'; import { requestClient } from '#/api/request'; enum Api { menuList = '/system/menu/list', menuTreeSelect = '/system/menu/treeselect', roleMenuTree = '/system/menu/roleMenuTreeselect', root = '/system/menu', tenantPackageMenuTreeselect = '/system/menu/tenantPackageMenuTreeselect', } /** * 菜单列表 * @param params 参数 * @returns 列表 */ export function menuList(params?: MenuQuery) { return requestClient.get(Api.menuList, { params }); } /** * 菜单详情 * @param menuId 菜单id * @returns 菜单详情 */ export function menuInfo(menuId: ID) { return requestClient.get(`${Api.root}/${menuId}`); } /** * 菜单新增 * @param data 参数 */ export function menuAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 菜单更新 * @param data 参数 */ export function menuUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 菜单删除 * @param menuIds ids */ export function menuRemove(menuIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${menuIds}`); } /** * 返回对应角色的菜单 * @param roleId id * @returns resp */ export function roleMenuTreeSelect(roleId: ID) { return requestClient.get(`${Api.roleMenuTree}/${roleId}`); } /** * 下拉框使用 返回所有的菜单 * @returns [] */ export function menuTreeSelect() { return requestClient.get(Api.menuTreeSelect); } /** * 租户套餐使用 * @param packageId packageId * @returns resp */ export function tenantPackageMenuTreeSelect(packageId: ID) { return requestClient.get( `${Api.tenantPackageMenuTreeselect}/${packageId}`, ); } /** * 批量删除菜单 * @param menuIds 菜单ids * @returns void */ export function menuCascadeRemove(menuIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/cascade/${menuIds}`); } ================================================ FILE: apps/web-antd/src/api/system/menu/model.d.ts ================================================ export interface Menu { createBy?: any; createTime: string; updateBy?: any; updateTime?: any; remark?: any; menuId: number; menuName: string; parentName?: string; parentId: number; orderNum: number; path: string; component?: string; query: string; isFrame: string; isCache: string; menuType: string; visible: string; status: string; perms: string; icon: string; children: Menu[]; } /** * @description 菜单信息 * @param label 菜单名称 */ export interface MenuOption { id: number; parentId: number; label: string; weight: number; children: MenuOption[]; key: string; // 实际上不存在 ide报错 menuType: string; icon: string; } /** * @description 菜单返回 * @param checkedKeys 选中的菜单id * @param menus 菜单信息 */ export interface MenuResp { checkedKeys: number[]; menus: MenuOption[]; } /** * 菜单表单查询 */ export interface MenuQuery { menuName?: string; visible?: string; status?: string; } ================================================ FILE: apps/web-antd/src/api/system/notice/index.ts ================================================ import type { Notice } from './model'; import type { ID, IDS, PageQuery } from '#/api/common'; import { requestClient } from '#/api/request'; enum Api { noticeList = '/system/notice/list', root = '/system/notice', } /** * 通知公告分页 * @param params 分页参数 * @returns 分页结果 */ export function noticeList(params?: PageQuery) { return requestClient.get(Api.noticeList, { params }); } /** * 通知公告详情 * @param noticeId id * @returns 详情 */ export function noticeInfo(noticeId: ID) { return requestClient.get(`${Api.root}/${noticeId}`); } /** * 通知公告新增 * @param data 参数 */ export function noticeAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 通知公告更新 * @param data 参数 */ export function noticeUpdate(data: any) { return requestClient.putWithMsg(Api.root, data); } /** * 通知公告删除 * @param noticeIds ids */ export function noticeRemove(noticeIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${noticeIds}`); } ================================================ FILE: apps/web-antd/src/api/system/notice/model.d.ts ================================================ export interface Notice { noticeId: number; noticeTitle: string; noticeType: string; noticeContent: string; status: string; remark: string; createBy: number; createByName: string; createTime: string; } ================================================ FILE: apps/web-antd/src/api/system/oss/index.ts ================================================ import type { AxiosRequestConfig } from '@vben/request'; import type { OssFile } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { ContentTypeEnum } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { ossDownload = '/resource/oss/download', ossInfo = '/resource/oss/listByIds', ossList = '/resource/oss/list', ossUpload = '/resource/oss/upload', root = '/resource/oss', } /** * 文件list * @param params 参数 * @returns 分页 */ export function ossList(params?: PageQuery) { return requestClient.get>(Api.ossList, { params }); } /** * 查询文件信息 返回为数组 * @param ossIds id数组 * @returns 信息数组 */ export function ossInfo(ossIds: ID | IDS) { return requestClient.get(`${Api.ossInfo}/${ossIds}`); } /** * @deprecated 使用apps/web-antd/src/api/core/upload.ts uploadApi方法 * @param file 文件 * @returns void */ export function ossUpload(file: Blob | File) { const formData = new FormData(); formData.append('file', file); return requestClient.postWithMsg(Api.ossUpload, formData, { headers: { 'Content-Type': ContentTypeEnum.FORM_DATA }, timeout: 30 * 1000, }); } /** * 下载文件 返回为二进制 * @param ossId ossId * @param onDownloadProgress 下载进度(可选) * @returns blob */ export function ossDownload( ossId: ID, onDownloadProgress?: AxiosRequestConfig['onDownloadProgress'], ) { return requestClient.get(`${Api.ossDownload}/${ossId}`, { responseType: 'blob', timeout: 30 * 1000, isTransformResponse: false, onDownloadProgress, }); } /** * 在使用浏览器原生下载前检测是否登录 * 这里的方案为请求一次接口 如果登录超时会走到response的401逻辑 * 如果没有listByIds的权限 也不会弹出无权限提示 * 仅仅是为了检测token是否有效使用 * * @returns void */ export function checkLoginBeforeDownload() { return requestClient.get(`${Api.ossInfo}/1`, { errorMessageMode: 'none', }); } /** * 删除文件 * @param ossIds id数组 * @returns void */ export function ossRemove(ossIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${ossIds}`); } ================================================ FILE: apps/web-antd/src/api/system/oss/model.d.ts ================================================ export interface OssFile { ossId: string; fileName: string; originalName: string; fileSuffix: string; url: string; createTime: string; createBy: number; createByName: string; service: string; } export interface OssConfig { ossConfigId: number; configKey: string; accessKey: string; secretKey: string; bucketName: string; prefix: string; endpoint: string; domain: string; isHttps: string; region: string; status: string; ext1: string; remark: string; accessPolicy: string; } ================================================ FILE: apps/web-antd/src/api/system/oss-config/index.ts ================================================ import type { OssConfig } from './model'; import type { ID, IDS, PageQuery } from '#/api/common'; import { requestClient } from '#/api/request'; enum Api { ossConfigChangeStatus = '/resource/oss/config/changeStatus', ossConfigList = '/resource/oss/config/list', root = '/resource/oss/config', } // 获取OSS配置列表 export function ossConfigList(params?: PageQuery) { return requestClient.get(Api.ossConfigList, { params }); } // 获取OSS配置的信息 export function ossConfigInfo(ossConfigId: ID) { return requestClient.get(`${Api.root}/${ossConfigId}`); } // 添加新的OSS配置 export function ossConfigAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } // 更新现有的OSS配置 export function ossConfigUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } // 删除OSS配置 export function ossConfigRemove(ossConfigIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${ossConfigIds}`); } // 更改OSS配置的状态 export function ossConfigChangeStatus(data: any) { const requestData: Partial = { ossConfigId: data.ossConfigId, status: data.status, configKey: data.configKey, }; return requestClient.putWithMsg(Api.ossConfigChangeStatus, requestData); } ================================================ FILE: apps/web-antd/src/api/system/oss-config/model.d.ts ================================================ export interface OssConfig { ossConfigId: number; configKey: string; accessKey: string; secretKey: string; bucketName: string; prefix: string; endpoint: string; domain: string; isHttps: string; region: string; status: string; ext1: string; remark: string; accessPolicy: string; } ================================================ FILE: apps/web-antd/src/api/system/post/index.ts ================================================ import type { DeptTree } from '../user/model'; import type { Post } from './model'; import type { ID, IDS, PageQuery } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { postExport = '/system/post/export', postList = '/system/post/list', postSelect = '/system/post/optionselect', root = '/system/post', } /** * 获取岗位列表 * @param params 参数 * @returns Post[] */ export function postList(params?: PageQuery) { return requestClient.get(Api.postList, { params }); } /** * 导出岗位信息 * @param data 请求参数 * @returns blob */ export function postExport(data: Partial) { return commonExport(Api.postExport, data); } /** * 查询岗位信息 * @param postId id * @returns 岗位信息 */ export function postInfo(postId: ID) { return requestClient.get(`${Api.root}/${postId}`); } /** * 岗位新增 * @param data 参数 * @returns void */ export function postAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 岗位更新 * @param data 参数 * @returns void */ export function postUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 岗位删除 * @param postIds ids * @returns void */ export function postRemove(postIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${postIds}`); } /** * 根据部门id获取岗位下拉列表 * @param deptId 部门id * @returns 岗位 */ export function postOptionSelect(deptId: ID) { return requestClient.get(Api.postSelect, { params: { deptId } }); } /** * 岗位专用 - 获取部门树 * @returns 部门树 */ export function postDeptTreeSelect() { return requestClient.get('/system/post/deptTree'); } ================================================ FILE: apps/web-antd/src/api/system/post/model.d.ts ================================================ /** * @description: Post interface */ export interface Post { postId: number; postCode: string; postName: string; postSort: number; status: string; remark: string; createTime: string; } ================================================ FILE: apps/web-antd/src/api/system/profile/index.ts ================================================ import type { FileCallBack, UpdatePasswordParam, UserProfile } from './model'; import { buildUUID } from '@vben/utils'; import { requestClient } from '#/api/request'; enum Api { root = '/system/user/profile', updateAvatar = '/system/user/profile/avatar', updatePassword = '/system/user/profile/updatePwd', } /** * 用户个人主页信息 * @returns userInformation */ export function userProfile() { return requestClient.get(Api.root); } /** * 更新用户个人主页信息 * @param data * @returns void */ export function userProfileUpdate(data: any) { return requestClient.putWithMsg(Api.root, data); } /** * 用户修改密码 (需要加密) * @param data * @returns void */ export function userUpdatePassword(data: UpdatePasswordParam) { return requestClient.putWithMsg(Api.updatePassword, data, { encrypt: true, }); } /** * 用户更新个人头像 * @param fileCallback data * @returns void */ export function userUpdateAvatar(fileCallback: FileCallBack) { /** 直接点击头像上传 filename为空 由于后台通过拓展名判断(默认文件名blob) 会上传失败 */ let { file } = fileCallback; const { filename } = fileCallback; /** * Blob转File类型 * 1. 在直接点击确认 filename为空 取uuid作为文件名 * 2. 选择上传必须转为File类型 Blob类型上传后台获取文件名为空 */ file = filename ? new File([file], filename) : new File([file], `${buildUUID()}.png`); return requestClient.post( Api.updateAvatar, { avatarfile: file, }, { headers: { 'Content-Type': 'multipart/form-data' } }, ); } ================================================ FILE: apps/web-antd/src/api/system/profile/model.d.ts ================================================ export interface Dept { deptId: number; parentId: number; parentName?: any; ancestors: string; deptName: string; orderNum: number; leader: string; phone?: any; email: string; status: string; createTime?: any; } export interface Role { roleId: number; roleName: string; roleKey: string; roleSort: number; dataScope: string; menuCheckStrictly?: any; deptCheckStrictly?: any; status: string; remark: string; createTime?: any; flag: boolean; superAdmin: boolean; } export interface User { userId: number; tenantId: string; deptId: number; userName: string; nickName: string; userType: string; email: string; phonenumber: string; sex: string; avatar: string; status: string; loginIp: string; loginDate: string; remark: string; createTime: string; dept: Dept; roles: Role[]; roleIds?: string[]; postIds?: string[]; roleId: number; deptName: string; } /** * @description 用户个人主页信息 * @param user 用户信息 * @param roleGroup 角色名称 * @param postGroup 岗位名称 */ export interface UserProfile { user: User; roleGroup: string; postGroup: string; } export interface UpdatePasswordParam { oldPassword: string; newPassword: string; } interface FileCallBack { name: string; file: Blob; filename: string; } ================================================ FILE: apps/web-antd/src/api/system/role/index.ts ================================================ import type { User } from '../user/model'; import type { DeptResp, Role } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { roleAllocatedList = '/system/role/authUser/allocatedList', roleAuthCancel = '/system/role/authUser/cancel', roleAuthCancelAll = '/system/role/authUser/cancelAll', roleAuthSelectAll = '/system/role/authUser/selectAll', roleChangeStatus = '/system/role/changeStatus', roleDataScope = '/system/role/dataScope', roleDeptTree = '/system/role/deptTree', roleExport = '/system/role/export', roleList = '/system/role/list', roleOptionSelect = '/system/role/optionselect', roleUnallocatedList = '/system/role/authUser/unallocatedList', root = '/system/role', } /** * 查询角色分页列表 * @param params 搜索条件 * @returns 分页列表 */ export function roleList(params?: PageQuery) { return requestClient.get>(Api.roleList, { params }); } /** * 导出角色信息 * @param data 查询参数 * @returns blob */ export function roleExport(data: Partial) { return commonExport(Api.roleExport, data); } /** * 查询角色信息 * @param roleId 角色id * @returns 角色信息 */ export function roleInfo(roleId: ID) { return requestClient.get(`${Api.root}/${roleId}`); } /** * 角色新增 * @param data 参数 * @returns void */ export function roleAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 角色更新 * @param data 参数 * @returns void */ export function roleUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 修改角色状态 * @param data 参数 * @returns void */ export function roleChangeStatus(data: Partial) { const requestData = { roleId: data.roleId, status: data.status, }; return requestClient.putWithMsg(Api.roleChangeStatus, requestData); } /** * 角色删除 * @param roleIds ids * @returns void */ export function roleRemove(roleIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${roleIds}`); } /** * 更新数据权限 * @param data * @returns void */ export function roleDataScope(data: any) { return requestClient.putWithMsg(Api.roleDataScope, data); } /** * @deprecated 全局并没有用到这个方法 */ export function roleOptionSelect(params?: any) { return requestClient.get(Api.roleOptionSelect, { params }); } /** * 已分配角色的用户分页 * @param params 请求参数 * @returns 分页 */ export function roleAllocatedList(params?: PageQuery) { return requestClient.get>(Api.roleAllocatedList, { params }); } /** * 未授权的用户 * @param params * @returns void */ export function roleUnallocatedList(params: any) { return requestClient.get>(Api.roleUnallocatedList, { params, }); } /** * 取消用户角色授权 * @returns void */ export function roleAuthCancel(data: { roleId: ID; userId: ID }) { return requestClient.putWithMsg(Api.roleAuthCancel, data); } /** * 批量取消授权 * @param roleId 角色ID * @param userIds 用户ID集合 * @returns void */ export function roleAuthCancelAll(roleId: ID, userIds: IDS) { return requestClient.putWithMsg( `${Api.roleAuthCancelAll}?roleId=${roleId}&userIds=${userIds.join(',')}`, ); } /** * 批量授权用户 * @param roleId 角色ID * @param userIds 用户ID集合 * @returns void */ export function roleSelectAll(roleId: ID, userIds: IDS) { return requestClient.putWithMsg( `${Api.roleAuthSelectAll}?roleId=${roleId}&userIds=${userIds.join(',')}`, ); } /** * 根据角色id获取部门树 * @param roleId 角色id * @returns DeptResp */ export function roleDeptTree(roleId: ID) { return requestClient.get(`${Api.roleDeptTree}/${roleId}`); } ================================================ FILE: apps/web-antd/src/api/system/role/model.d.ts ================================================ export interface Role { roleId: number; roleName: string; roleKey: string; roleSort: number; dataScope: string; menuCheckStrictly: boolean; deptCheckStrictly: boolean; status: string; remark: string; createTime: string; // 用户是否存在此角色标识 默认不存在 flag: boolean; superAdmin: boolean; } export interface DeptOption { id: number; parentId: number; label: string; weight: number; children: DeptOption[]; key: string; // 实际上不存在 ide报错 } export interface DeptResp { checkedKeys: number[]; depts: DeptOption[]; } ================================================ FILE: apps/web-antd/src/api/system/social/index.ts ================================================ import type { SocialInfo } from './model'; import type { ID } from '#/api/common'; import { requestClient } from '#/api/request'; enum Api { root = '/system/social', socialList = '/system/social/list', } /** * 获取绑定的社交信息列表 * @returns info */ export function socialList() { return requestClient.get(Api.socialList); } /** * @deprecated 并没有用到这个方法 */ export function socialInfo(id: ID) { return requestClient.get(`${Api.root}/${id}`); } ================================================ FILE: apps/web-antd/src/api/system/social/model.d.ts ================================================ export interface SocialInfo { id: string; userId: number; tenantId: string; authId: string; source: string; accessToken: string; expireIn: number; refreshToken: string; openId: string; userName: string; nickName: string; email: string; avatar: string; accessCode?: any; unionId?: any; scope: string; tokenType: string; idToken?: any; macAlgorithm?: any; macKey?: any; code?: any; oauthToken?: any; oauthTokenSecret?: any; createTime: string; } ================================================ FILE: apps/web-antd/src/api/system/tenant/index.ts ================================================ import type { Tenant } from './model'; import type { ID, IDS, PageQuery } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { dictSync = '/system/tenant/syncTenantDict', root = '/system/tenant', tenantDynamic = '/system/tenant/dynamic', tenantDynamicClear = '/system/tenant/dynamic/clear', tenantExport = '/system/tenant/export', tenantList = '/system/tenant/list', tenantStatus = '/system/tenant/changeStatus', tenantSyncPackage = '/system/tenant/syncTenantPackage', } /** * 查询租户分页列表 * @param params 参数 * @returns 分页 */ export function tenantList(params?: PageQuery) { return requestClient.get(Api.tenantList, { params }); } /** * 租户导出 * @param data data * @returns void */ export function tenantExport(data: Partial) { return commonExport(Api.tenantExport, data); } /** * 查询租户信息 * @param id id * @returns 租户信息 */ export function tenantInfo(id: ID) { return requestClient.get(`${Api.root}/${id}`); } /** * 新增租户 必须开启加密 * @param data data * @returns void */ export function tenantAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data, { encrypt: true }); } /** * 租户更新 * @param data data * @returns void */ export function tenantUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 租户状态更新 * @param data data * @returns void */ export function tenantStatusChange(data: Partial) { const requestData = { id: data.id, tenantId: data.tenantId, status: data.status, }; return requestClient.putWithMsg(Api.tenantStatus, requestData); } /** * 租户删除 * @param ids ids * @returns void */ export function tenantRemove(ids: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${ids}`); } /** * 动态切换租户 * @param tenantId 租户ID * @returns void */ export function tenantDynamicToggle(tenantId: string) { return requestClient.get(`${Api.tenantDynamic}/${tenantId}`); } /** * 清除 动态切换租户 * @returns void */ export function tenantDynamicClear() { return requestClient.get(Api.tenantDynamicClear); } /** * 租户套餐同步 * @param tenantId 租户id * @param packageId 套餐id * @returns void */ export function tenantSyncPackage(tenantId: string, packageId: string) { return requestClient.get(Api.tenantSyncPackage, { params: { packageId, tenantId }, successMessageMode: 'message', }); } /** * 同步租户字典 * @param tenantId 租户ID * @returns void */ export function dictSyncTenant(tenantId?: string) { return requestClient.get(Api.dictSync, { params: { tenantId }, successMessageMode: 'message', }); } /** * 同步租户配置 * @returns void */ export function syncTenantConfig() { return requestClient.get('/system/tenant/syncTenantConfig', { successMessageMode: 'message', }); } ================================================ FILE: apps/web-antd/src/api/system/tenant/model.d.ts ================================================ export interface Tenant { accountCount: number; address?: string; companyName: string; contactPhone: string; contactUserName: string; domain?: string; expireTime?: string; id: number; intro: string; licenseNumber?: any; packageId: string; remark?: string; status: string; tenantId: string; } ================================================ FILE: apps/web-antd/src/api/system/tenant-package/index.ts ================================================ import type { TenantPackage } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { packageChangeStatus = '/system/tenant/package/changeStatus', packageExport = '/system/tenant/package/export', packageList = '/system/tenant/package/list', packageSelectList = '/system/tenant/package/selectList', root = '/system/tenant/package', } /** * 租户套餐分页列表 * @param params 请求参数 * @returns 分页列表 */ export function packageList(params?: PageQuery) { return requestClient.get>(Api.packageList, { params, }); } /** * 租户套餐下拉框 * @returns 下拉框 */ export function packageSelectList() { return requestClient.get(Api.packageSelectList); } /** * 租户套餐导出 * @param data 参数 * @returns blob */ export function packageExport(data: Partial) { return commonExport(Api.packageExport, data); } /** * 租户套餐信息 * @param id id * @returns 信息 */ export function packageInfo(id: ID) { return requestClient.get(`${Api.root}/${id}`); } /** * 租户套餐新增 * @param data data * @returns void */ export function packageAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 租户套餐更新 * @param data data * @returns void */ export function packageUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 租户套餐状态变更 * @param data data * @returns void */ export function packageChangeStatus(data: Partial) { const packageId = { packageId: data.packageId, status: data.status, }; return requestClient.putWithMsg(Api.packageChangeStatus, packageId); } /** * 租户套餐移除 * @param ids ids * @returns void */ export function packageRemove(ids: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${ids}`); } ================================================ FILE: apps/web-antd/src/api/system/tenant-package/model.d.ts ================================================ /** * @description 租户套餐 * @param packageId id * @param packageName 名称 * @param menuIds 菜单id 格式为[1,2,3] 返回为string 提交为数组 * @param remark 备注 * @param menuCheckStrictly 是否关联父节点 * @param status 状态 */ export interface TenantPackage { packageId: string; packageName: string; menuIds: number[] | string; remark: string; menuCheckStrictly: boolean; status: string; } ================================================ FILE: apps/web-antd/src/api/system/user/index.ts ================================================ import type { DeptTree, ResetPwdParam, User, UserImportParam, UserInfoResponse, } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { commonExport, ContentTypeEnum } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { deptTree = '/system/user/deptTree', listDeptUsers = '/system/user/list/dept', root = '/system/user', userAuthRole = '/system/user/authRole', userExport = '/system/user/export', userImport = '/system/user/importData', userImportTemplate = '/system/user/importTemplate', userList = '/system/user/list', userResetPassword = '/system/user/resetPwd', userStatusChange = '/system/user/changeStatus', } /** * 获取用户列表 * @param params * @returns User */ export function userList(params?: PageQuery) { return requestClient.get>(Api.userList, { params }); } /** * 导出excel * @param data data * @returns blob */ export function userExport(data: Partial) { return commonExport(Api.userExport, data); } /** * 从excel导入用户 * @param data * @returns void */ export function userImportData(data: UserImportParam) { return requestClient.post<{ code: number; msg: string }>( Api.userImport, data, { headers: { 'Content-Type': ContentTypeEnum.FORM_DATA, }, isTransformResponse: false, }, ); } /** * 下载用户导入模板 * @returns blob */ export function downloadImportTemplate() { return requestClient.post( Api.userImportTemplate, {}, { isTransformResponse: false, responseType: 'blob', }, ); } /** * 可以不传ID 返回部门和角色options 需要获得原始数据 * 不传ID时一定要带最后的/ * @param userId 用户ID * @returns 用户信息 */ export function findUserInfo(userId?: ID) { const url = userId ? `${Api.root}/${userId}` : `${Api.root}/`; return requestClient.get(url); } /** * 新增用户 * @param data data * @returns void */ export function userAdd(data: Partial) { return requestClient.postWithMsg(Api.root, data); } /** * 更新用户 * @param data data * @returns void */ export function userUpdate(data: Partial) { return requestClient.putWithMsg(Api.root, data); } /** * 更新用户状态 * @param data data * @returns void */ export function userStatusChange(data: Partial) { const requestData = { userId: data.userId, status: data.status, }; return requestClient.putWithMsg(Api.userStatusChange, requestData); } /** * 删除用户 * @param userIds 用户ID数组 * @returns void */ export function userRemove(userIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${userIds}`); } /** * 重置用户密码 需要加密 * @param data * @returns void */ export function userResetPassword(data: ResetPwdParam) { return requestClient.putWithMsg(Api.userResetPassword, data, { encrypt: true, }); } /** * 这个方法未调用过 * @param userId * @returns void */ export function getUserAuthRole(userId: ID) { return requestClient.get(`${Api.userAuthRole}/${userId}`); } /** * 这个方法未调用过 * @param userId * @returns void */ export function userAuthRoleUpdate(userId: ID, roleIds: number[]) { return requestClient.putWithMsg(Api.userAuthRole, { roleIds, userId }); } /** * 获取部门树 * @returns 部门树数组 */ export function getDeptTree() { return requestClient.get(Api.deptTree); } /** * 获取部门下的所有用户信息 */ export function listUserByDeptId(deptId: ID) { return requestClient.get(`${Api.listDeptUsers}/${deptId}`); } ================================================ FILE: apps/web-antd/src/api/system/user/model.d.ts ================================================ /** * @description: 用户导入 * @param updateSupport 是否覆盖数据 * @param file excel文件 */ export interface UserImportParam { updateSupport: boolean; file: Blob | File; } /** * @description: 重置密码 */ export interface ResetPwdParam { userId: string; password: string; } export interface Dept { deptId: number; parentId: number; parentName?: string; ancestors: string; deptName: string; orderNum: number; leader: string; phone?: string; email?: string; status: string; createTime?: string; } export interface Role { roleId: string; roleName: string; roleKey: string; roleSort: number; dataScope: string; menuCheckStrictly?: boolean; deptCheckStrictly?: boolean; status: string; remark: string; createTime?: string; flag: boolean; superAdmin: boolean; } export interface User { userId: string; tenantId: string; deptId: number; userName: string; nickName: string; userType: string; email: string; phonenumber: string; sex: string; avatar?: string; status: string; loginIp: string; loginDate: string; remark: string; createTime: string; dept: Dept; roles: Role[]; roleIds?: string[]; postIds?: number[]; roleId: string; deptName: string; } export interface Post { postId: number; postCode: string; postName: string; postSort: number; status: string; remark: string; createTime: string; } /** * @description 用户信息 * @param user 用户个人信息 * @param roleIds 角色IDS 不传id为空 * @param roles 所有的角色 * @param postIds 岗位IDS 不传id为空 * @param posts 所有的岗位 */ export interface UserInfoResponse { user?: User; roleIds?: string[]; roles: Role[]; postIds?: number[]; posts?: Post[]; } /** * @description: 部门树 */ export interface DeptTree { id: number; /** * antd组件必须要这个属性 实际是没有这个属性的 */ key: string; parentId: number; label: string; weight: number; children?: DeptTree[]; } export interface DeptTreeData { id: number; label: string; children?: DeptTreeData[]; } ================================================ FILE: apps/web-antd/src/api/tool/gen/index.ts ================================================ import type { GenInfo } from './model'; import type { ID, IDS, PageQuery } from '#/api/common'; import { ContentTypeEnum } from '#/api/helper'; import { requestClient } from '#/api/request'; enum Api { batchGenCode = '/tool/gen/batchGenCode', columnList = '/tool/gen/column', dataSourceNames = '/tool/gen/getDataNames', download = '/tool/gen/download', genCode = '/tool/gen/genCode', generatedList = '/tool/gen/list', importTable = '/tool/gen/importTable', preview = '/tool/gen/preview', readyToGenList = '/tool/gen/db/list', root = '/tool/gen', syncDb = '/tool/gen/synchDb', } // 查询代码生成列表 export function generatedList(params?: PageQuery) { return requestClient.get(Api.generatedList, { params }); } // 修改代码生成业务 export function genInfo(tableId: ID) { return requestClient.get(`${Api.root}/${tableId}`); } // 查询数据库列表 export function readyToGenList(params?: PageQuery) { return requestClient.get(Api.readyToGenList, { params }); } // 查询数据表字段列表 export function columnList(tableId: ID) { return requestClient.get(`${Api.columnList}/${tableId}`); } /** * 导入表结构(保存) * @param tables table名称数组 如sys_a, sys_b * @param dataName 数据源名称 * @returns ret */ export function importTable(tables: string | string[], dataName: string) { return requestClient.postWithMsg( Api.importTable, { dataName, tables }, { headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, }, ); } // 修改保存代码生成业务 export function editSave(data: any) { return requestClient.putWithMsg(Api.root, data); } // 删除代码生成 export function genRemove(tableIds: IDS) { return requestClient.deleteWithMsg(`${Api.root}/${tableIds}`); } // 预览代码 export function previewCode(tableId: ID) { return requestClient.get<{ [key: string]: string }>( `${Api.preview}/${tableId}`, ); } // 生成代码(下载方式) export function genDownload(tableId: ID) { return requestClient.get(`${Api.download}/${tableId}`); } // 生成代码(自定义路径) export function genWithPath(tableId: ID) { return requestClient.get(`${Api.genCode}/${tableId}`); } // 同步数据库 export function syncDb(tableId: ID) { return requestClient.get(`${Api.syncDb}/${tableId}`, { successMessageMode: 'message', }); } // 批量生成代码 export function batchGenCode(tableIdStr: ID | IDS) { return requestClient.get(Api.batchGenCode, { isTransformResponse: false, params: { tableIdStr }, responseType: 'blob', }); } // 查询数据源名称列表 export function getDataSourceNames() { return requestClient.get(Api.dataSourceNames); } ================================================ FILE: apps/web-antd/src/api/tool/gen/model.d.ts ================================================ export interface Column { createDept?: any; createBy?: any; createTime?: any; updateBy?: any; updateTime?: any; columnId: string; tableId: string; columnName: string; columnComment: string; columnType: string; javaType: string; javaField: string; isPk: string; isIncrement: string; isRequired: string; isInsert?: any; isEdit: string; isList: string; isQuery?: any; queryType: string; htmlType: string; dictType: string; sort: number; list: boolean; required: boolean; pk: boolean; insert: boolean; edit: boolean; usableColumn: boolean; superColumn: boolean; increment: boolean; query: boolean; capJavaField: string; } export interface Table { createDept?: any; createBy?: any; createTime?: any; updateBy?: any; updateTime?: any; tableId: string; dataName: string; tableName: string; tableComment: string; subTableName?: any; subTableFkName?: any; className: string; tplCategory: string; packageName: string; moduleName: string; businessName: string; functionName: string; functionAuthor: string; genType?: any; genPath?: any; pkColumn?: any; columns: Column[]; options?: any; remark?: any; treeCode?: any; treeParentCode?: any; treeName?: any; menuIds?: any; parentMenuId?: any; parentMenuName?: any; tree: boolean; crud: boolean; } export interface Row { createDept: number; createBy: number; createTime: string; updateBy: number; updateTime: string; columnId: string; tableId: string; columnName: string; columnComment: string; columnType: string; javaType: string; javaField: string; isPk: string; isIncrement: string; isRequired: string; isInsert?: any; isEdit: string; isList: string; isQuery?: any; queryType: string; htmlType: string; dictType: string; sort: number; list: boolean; required: boolean; pk: boolean; insert: boolean; edit: boolean; usableColumn: boolean; superColumn: boolean; increment: boolean; query: boolean; capJavaField: string; } export interface Column { createDept?: any; createBy?: any; createTime?: any; updateBy?: any; updateTime?: any; columnId: string; tableId: string; columnName: string; columnComment: string; columnType: string; javaType: string; javaField: string; isPk: string; isIncrement: string; isRequired: string; isInsert?: any; isEdit: string; isList: string; isQuery?: any; queryType: string; htmlType: string; dictType: string; sort: number; list: boolean; required: boolean; pk: boolean; insert: boolean; edit: boolean; usableColumn: boolean; superColumn: boolean; increment: boolean; query: boolean; capJavaField: string; } export interface Info { createDept?: any; createBy?: any; createTime?: any; updateBy?: any; updateTime?: any; tableId: string; dataName: string; tableName: string; tableComment: string; subTableName?: any; subTableFkName?: any; className: string; tplCategory: string; packageName: string; moduleName: string; businessName: string; functionName: string; functionAuthor: string; genType: string; genPath: string; pkColumn?: any; columns: Column[]; options?: any; remark?: any; treeCode?: any; treeParentCode?: any; treeName?: any; menuIds?: any; parentMenuId?: any; parentMenuName?: any; tree: boolean; crud: boolean; // 树表需要添加此属性 params?: any; popupComponent?: string; formComponent?: string; } export interface GenInfo { tables: Table[]; rows: Row[]; info: Info; } ================================================ FILE: apps/web-antd/src/api/workflow/category/index.ts ================================================ import type { CategoryForm, CategoryQuery, CategoryTree, CategoryVO, } from './model'; import type { ID, IDS } from '#/api/common'; import { requestClient } from '#/api/request'; /** * 获取流程分类树列表 * @returns tree */ export function categoryTree() { return requestClient.get('/workflow/category/categoryTree'); } /** * 查询流程分类列表 * @param params * @returns 流程分类列表 */ export function categoryList(params?: CategoryQuery) { return requestClient.get(`/workflow/category/list`, { params }); } /** * 查询流程分类详情 * @param id id * @returns 流程分类详情 */ export function categoryInfo(id: ID) { return requestClient.get(`/workflow/category/${id}`); } /** * 新增流程分类 * @param data * @returns void */ export function categoryAdd(data: CategoryForm) { return requestClient.postWithMsg('/workflow/category', data); } /** * 更新流程分类 * @param data * @returns void */ export function categoryUpdate(data: CategoryForm) { return requestClient.putWithMsg('/workflow/category', data); } /** * 删除流程分类 * @param id id * @returns void */ export function categoryRemove(id: ID | IDS) { return requestClient.deleteWithMsg(`/workflow/category/${id}`); } ================================================ FILE: apps/web-antd/src/api/workflow/category/model.d.ts ================================================ import type { BaseEntity } from '#/api/common'; export interface CategoryVO { /** * 主键 */ id: number | string; /** * 分类名称 */ categoryName: string; /** * 分类编码 */ categoryCode: string; /** * 父级id */ parentId: number | string; /** * 排序 */ sortNum: number; /** * 子对象 */ children: CategoryVO[]; key: string; } export interface CategoryForm extends BaseEntity { /** * 主键 */ id?: number | string; /** * 分类名称 */ categoryName?: string; /** * 分类编码 */ categoryCode?: string; /** * 父级id */ parentId?: number | string; /** * 排序 */ sortNum?: number; } export interface CategoryQuery { /** * 分类名称 */ categoryName?: string; /** * 分类编码 */ categoryCode?: string; /** * 父级id */ parentId?: number | string; /** * 排序 */ sortNum?: number; /** * 日期范围参数 */ params?: any; } export interface CategoryTree { id: number; parentId: number; label: string; weight: number; children: CategoryTree[]; key: string; } ================================================ FILE: apps/web-antd/src/api/workflow/definition/index.ts ================================================ import type { ProcessDefinition } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { requestClient } from '#/api/request'; /** * 全部的流程定义 * @param params 查询参数 * @returns 分页 */ export function workflowDefinitionList(params?: PageQuery) { return requestClient.get>( '/workflow/definition/list', { params }, ); } /** * 未发布的流程定义 * @param params 查询参数 * @returns 分页 */ export function unPublishList(params?: PageQuery) { return requestClient.get>( '/workflow/definition/unPublishList', { params }, ); } /** * 获取历史流程定义列表 * @param flowCode * @returns ProcessDefinition[] */ export function getHisListByKey(flowCode: string) { return requestClient.get( `/workflow/definition/getHisListByKey/${flowCode}`, ); } /** * 获取流程定义详细信息 * @param id id * @returns ProcessDefinition */ export function workflowDefinitionInfo(id: ID) { return requestClient.get(`/workflow/definition/${id}`); } /** * 新增流程定义 * @param data */ export function workflowDefinitionAdd(data: any) { return requestClient.postWithMsg('/workflow/definition', data); } /** * 更新流程定义 * @param data */ export function workflowDefinitionUpdate(data: any) { return requestClient.putWithMsg('/workflow/definition', data); } /** * 发布流程定义 * @param id id * @returns boolean */ export function workflowDefinitionPublish(id: ID) { return requestClient.putWithMsg( `/workflow/definition/publish/${id}`, ); } /** * 取消发布流程定义 * @param id id * @returns boolean */ export function workflowDefinitionUnPublish(id: ID) { return requestClient.putWithMsg( `/workflow/definition/unPublish/${id}`, ); } /** * 删除流程定义 * @param ids idList */ export function workflowDefinitionDelete(ids: IDS) { return requestClient.deleteWithMsg(`/workflow/definition/${ids}`); } /** * 复制流程定义 * @param id id */ export function workflowDefinitionCopy(id: ID) { return requestClient.postWithMsg(`/workflow/definition/copy/${id}`); } /** * 导入流程定义 * @returns boolean */ export function workflowDefinitionImport(data: { category: ID; file: Blob | File; }) { return requestClient.postWithMsg( '/workflow/definition/importDef', data, { headers: { 'Content-Type': 'multipart/form-data' } }, ); } /** * 导出流程定义 * @param id id * @returns blob */ export function workflowDefinitionExport(id: ID) { return requestClient.postWithMsg( `/workflow/definition/exportDef/${id}`, {}, { responseType: 'blob', isTransformResponse: false, }, ); } /** * 获取流程定义xml字符串 * @param id id * @returns xml */ export function workflowDefinitionXml(id: ID) { return requestClient.get(`/workflow/definition/xmlString/${id}`); } /** * 激活/挂起流程定义 * @param id 流程定义id * @param active 激活/挂起 * @returns boolean */ export function workflowDefinitionActive(id: ID, active: boolean) { return requestClient.putWithMsg( `/workflow/definition/active/${id}?active=${active}`, ); } ================================================ FILE: apps/web-antd/src/api/workflow/definition/model.d.ts ================================================ export interface ProcessDefinition { id: string; createTime: string; updateTime: string; tenantId: string; delFlag: string; flowCode: string; flowName: string; category: string; categoryName: string; version: string; isPublish: number; formCustom: string; formPath: string; activityStatus: number; listenerType?: any; listenerPath?: any; ext?: any; } ================================================ FILE: apps/web-antd/src/api/workflow/instance/index.ts ================================================ import type { TaskInfo } from '../task/model'; import type { FlowInfoResponse, FlowInstanceVariableResp } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { requestClient } from '#/api/request'; /** * @param businessId 业务ID * @returns TaskInfo */ export function getTaskByBusinessId(businessId: string) { return requestClient.get( `/workflow/instance/getInfo/${businessId}`, ); } /** * 分页查询正在运行的流程实例 * @param params * @returns */ export function pageByRunning(params?: PageQuery) { return requestClient.get('/workflow/instance/pageByRunning', { params }); } /** * pageByFinish * @param params * @returns */ export function pageByFinish(params?: PageQuery) { return requestClient.get('/workflow/instance/pageByFinish', { params }); } /** * 按照业务id删除流程实例 * @param businessIds 业务id */ export function deleteByBusinessIds(businessIds: IDS) { return requestClient.deleteWithMsg( `/workflow/instance/deleteByBusinessIds${businessIds}`, ); } /** * 按照实例id删除流程实例 * @param instanceIds 实例id */ export function deleteByInstanceIds(instanceIds: IDS) { return requestClient.deleteWithMsg( `/workflow/instance/deleteByInstanceIds/${instanceIds}`, ); } /** * 撤销流程 * @param data */ export function cancelProcessApply(data: { businessId: ID; message?: string }) { return requestClient.putWithMsg( '/workflow/instance/cancelProcessApply', data, ); } /** * 激活/挂起流程实例 * @param instanceId * @param active */ export function workflowInstanceActive(instanceId: ID, active: boolean) { return requestClient.putWithMsg( `/workflow/instance/active/${instanceId}?active=${active}`, ); } /** * 获取当前登录人发起的流程实例 * @param params * @returns PageResult */ export function pageByCurrent(params?: PageQuery) { return requestClient.get>( '/workflow/instance/pageByCurrent', { params }, ); } /** * 获取流程图,流程记录 * @param businessId 业务标识 * @returns 流程图,流程记录 */ export function flowInfo(businessId: string) { return requestClient.get( `/workflow/instance/flowHisTaskList/${businessId}`, ); } /** * 获取流程变量 * @param instanceId * @returns Map */ export function instanceVariable(instanceId: string) { return requestClient.get( `/workflow/instance/instanceVariable/${instanceId}`, ); } /** * 作废流程 */ export function workflowInstanceInvalid(data: { comment?: string; id: string; }) { return requestClient.postWithMsg('/workflow/instance/invalid', data); } /** * 修改流程参数 * @param data 参数 * @param data.instanceId 实例ID * @param data.key 参数key * @param data.value 值 * @returns void */ export function updateFlowVariable(data: { instanceId: string; key: string; value: any; }) { return requestClient.putWithMsg( '/workflow/instance/updateVariable', data, ); } ================================================ FILE: apps/web-antd/src/api/workflow/instance/model.d.ts ================================================ export {}; export interface Flow { id: string; createTime: string; updateTime: string; tenantId: string; delFlag: string; definitionId: string; flowName?: any; instanceId: string; taskId: string; cooperateType: number; cooperateTypeName: string; businessId?: any; nodeCode: string; nodeName: string; nodeType: number; targetNodeCode: string; targetNodeName: string; approver: string; approveName: string; collaborator?: any; permissionList?: any; skipType: string; flowStatus: string; flowTaskStatus?: any; flowStatusName?: any; message: string; ext: null | string; createBy?: any; formCustom: string; formPath: string; flowCode?: any; version?: any; runDuration: string; nickName?: any; } export interface FlowInfoResponse { instanceId: string; list: Flow[]; } export interface FlowInstanceVariableResp { /** * json字符串 流程变量 */ variable: string; variableList: { key: string; value: any; }[]; } ================================================ FILE: apps/web-antd/src/api/workflow/spel/index.tsx ================================================ import type { Spel } from './model'; import type { ID, PageQuery, PageResult } from '#/api/common'; import { requestClient } from '#/api/request'; export function spelList(params?: PageQuery) { return requestClient.get>('/workflow/spel/list', { params }); } export function spelInfo(id: ID) { return requestClient.get(`/workflow/spel/${id}`); } export function spelAdd(data: Partial) { return requestClient.postWithMsg('/workflow/spel', data); } export function spelUpdate(data: Partial) { return requestClient.putWithMsg('/workflow/spel', data); } export function spelDelete(ids: ID[]) { return requestClient.deleteWithMsg(`/workflow/spel/${ids}`); } ================================================ FILE: apps/web-antd/src/api/workflow/spel/model.d.ts ================================================ export interface Spel { id: number; componentName: string; methodName: string; methodParams: string; viewSpel: string; status: string; remark: string; createTime: string; } ================================================ FILE: apps/web-antd/src/api/workflow/task/index.ts ================================================ import type { CompleteTaskReqData, NextNodeInfo, StartWorkFlowReqData, TaskInfo, TaskOperationData, TaskOperationType, } from './model'; import type { ID, IDS, PageQuery, PageResult } from '#/api/common'; import { requestClient } from '#/api/request'; /** * 启动任务 * @param data */ export function startWorkFlow(data: StartWorkFlowReqData) { return requestClient.post<{ processInstanceId: string; taskId: string; }>('/workflow/task/startWorkFlow', data); } /** * 办理任务 * @param data */ export function completeTask(data: CompleteTaskReqData) { return requestClient.postWithMsg('/workflow/task/completeTask', data); } /** * 查询当前用户的待办任务 * @param params */ export function pageByTaskWait(params?: PageQuery) { return requestClient.get>( '/workflow/task/pageByTaskWait', { params }, ); } /** * 查询当前用户的已办任务 * @param params */ export function pageByTaskFinish(params?: PageQuery) { return requestClient.get>( '/workflow/task/pageByTaskFinish', { params }, ); } /** * 查询所有待办任务 * @param params */ export function pageByAllTaskWait(params?: PageQuery) { return requestClient.get>( '/workflow/task/pageByAllTaskWait', { params }, ); } /** * 查询已办任务 * @param params */ export function pageByAllTaskFinish(params?: PageQuery) { return requestClient.get>( '/workflow/task/pageByAllTaskFinish', { params }, ); } /** * 查询当前用户的抄送 * @param params */ export function pageByTaskCopy(params?: PageQuery) { return requestClient.get>( '/workflow/task/pageByTaskCopy', { params }, ); } /** * 根据taskId查询代表任务 * @param taskId 任务id * @returns info */ export function getTaskByTaskId(taskId: string) { return requestClient.get(`/workflow/task/getTask/${taskId}`); } /** * 终止任务 */ export function terminationTask(data: { comment?: string; taskId: string }) { return requestClient.postWithMsg( '/workflow/task/terminationTask', data, ); } /** * 任务操作 * @param taskOperationData 参数 * @param taskOperation 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature */ export function taskOperation( taskOperationData: TaskOperationData, taskOperation: TaskOperationType, ) { return requestClient.postWithMsg( `/workflow/task/taskOperation/${taskOperation}`, taskOperationData, ); } /** * 修改任务办理人 * @param taskIdList 任务id * @param userId 办理人id */ export function updateAssignee(taskIdList: IDS, userId: ID) { return requestClient.putWithMsg( `/workflow/task/updateAssignee/${userId}`, taskIdList, ); } /** * 驳回审批 * @param data 参数 */ export function backProcess(data: any) { return requestClient.postWithMsg('/workflow/task/backProcess', data); } /** * 获取可驳回节点 * @param taskId 任务ID * @param nodeCode 当前节点编码 */ export function getBackTaskNode(taskId: string, nodeCode: string) { return requestClient.get<{ nodeCode: string; nodeName: string }[]>( `/workflow/task/getBackTaskNode/${taskId}/${nodeCode}`, ); } /** * 获取当前任务的所有办理人 * @param taskId 任务id */ export function currentTaskAllUser(taskId: ID) { return requestClient.get(`/workflow/task/currentTaskAllUser/${taskId}`); } /** * 获取下一节点 * @param data data * @param data.taskId taskId * @returns NextNodeInfo */ export function getNextNodeList(data: { taskId: string }) { return requestClient.post( '/workflow/task/getNextNodeList', data, ); } ================================================ FILE: apps/web-antd/src/api/workflow/task/model.d.ts ================================================ export interface ButtonWithPermission { code: string; value: null | string; show: boolean; } export interface TaskInfo { id: string; categoryName: string; createTime: string; updateTime: string; tenantId: string; delFlag?: any; definitionId: string; instanceId: string; flowName: string; businessId: string; nodeCode: string; nodeName: string; /** * >5.5.0版本才有自定义标题 */ businessTitle?: string; nodeType: number; permissionList?: any; userList?: any; formCustom: string; formPath: string; flowCode: string; version: string; flowStatus: string; flowStatusName: string; assigneeIds: string; assigneeNames: string; processedBy: string; type: string; nodeRatio?: string; createBy: string; createByName: string; targetNodeName?: string; buttonList: ButtonWithPermission[]; } export interface CompleteTaskReqData { messageType: string[]; flowCopyList: { userId: string; userName: string }[]; taskId: ID; taskVariables: Record; variables: any; // 附件ID 1,2,3,4形式 fileId?: string; // 选人 key为节点code value为用户ID join(,) assigneeMap: { [key: string]: string }; } export interface StartWorkFlowReqData { /** * 业务ID */ businessId: ID; /** * flowCode */ flowCode: string; /** * 流程变量 */ variables: Record; /** * 流程实例业务扩展业务对象 必须要有不能为null 可以为空对象 */ flowInstanceBizExtBo: Record; } export interface TaskOperationData { message?: string; taskId: ID; // 单个操作人 userId?: ID; // 多个操作人 userIds?: IDS; } /** * 操作类型,委派 delegateTask、转办 transferTask、加签 addSignature、减签 reductionSignature */ export type TaskOperationType = | 'addSignature' | 'delegateTask' | 'reductionSignature' | 'transferTask'; export interface NextNodeInfo { skipList: string[]; id: string; createTime: string; updateTime: string; tenantId: string; delFlag: string; nodeType: number; definitionId: string; nodeCode: string; nodeName: string; permissionFlag: string; nodeRatio: string; coordinate: string; version: string; anyNodeSkip: any; listenerType: any; listenerPath: any; handlerType: any; handlerPath: any; formCustom: string; formPath: any; ext: string; } ================================================ FILE: apps/web-antd/src/app.vue ================================================ ================================================ FILE: apps/web-antd/src/bootstrap.ts ================================================ import { createApp, watchEffect } from 'vue'; import { registerAccessDirective } from '@vben/access'; import { registerLoadingDirective } from '@vben/common-ui/es/loading'; import { preferences } from '@vben/preferences'; import { initStores } from '@vben/stores'; import '@vben/styles'; import '@vben/styles/antd'; import { useTitle } from '@vueuse/core'; import { setupGlobalComponent } from '#/components/global'; import { $t, setupI18n } from '#/locales'; import { initComponentAdapter } from './adapter/component'; import { initSetupVbenForm } from './adapter/form'; import App from './app.vue'; import { router } from './router'; async function bootstrap(namespace: string) { // 初始化组件适配器 await initComponentAdapter(); // 初始化表单组件 await initSetupVbenForm(); // // 设置弹窗的默认配置 // setDefaultModalProps({ // fullscreenButton: false, // }); // // 设置抽屉的默认配置 // setDefaultDrawerProps({ // zIndex: 1020, // }); const app = createApp(App); // 全局组件 setupGlobalComponent(app); // 注册v-loading指令 registerLoadingDirective(app, { loading: 'loading', // 在这里可以自定义指令名称,也可以明确提供false表示不注册这个指令 spinning: 'spinning', }); // 国际化 i18n 配置 await setupI18n(app); // 配置 pinia-tore await initStores(app, { namespace }); // 安装权限指令 registerAccessDirective(app); // 初始化 tippy const { initTippy } = await import('@vben/common-ui/es/tippy'); initTippy(app); // 配置路由及路由守卫 app.use(router); // 配置Motion插件 const { MotionPlugin } = await import('@vben/plugins/motion'); app.use(MotionPlugin); // 动态更新标题 watchEffect(() => { if (preferences.app.dynamicTitle) { const routeTitle = router.currentRoute.value.meta?.title; const pageTitle = (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; useTitle(pageTitle); } }); app.mount('#app'); } export { bootstrap }; ================================================ FILE: apps/web-antd/src/components/cropper/index.ts ================================================ export { default as CropperAvatar } from './src/cropper-avatar.vue'; export { default as CropperImage } from './src/cropper.vue'; export type { Cropper } from './src/typing'; ================================================ FILE: apps/web-antd/src/components/cropper/src/cropper-avatar.vue ================================================ ================================================ FILE: apps/web-antd/src/components/cropper/src/cropper-modal.vue ================================================ ================================================ FILE: apps/web-antd/src/components/cropper/src/cropper.vue ================================================ ================================================ FILE: apps/web-antd/src/components/cropper/src/typing.ts ================================================ import type Cropper from 'cropperjs'; export interface CropendResult { imgBase64: string; imgInfo: Cropper.Data; } export type { Cropper }; ================================================ FILE: apps/web-antd/src/components/description/index.ts ================================================ export { default as Description } from './src/description.vue'; export * from './src/typing'; export { useDescription } from './src/useDescription'; ================================================ FILE: apps/web-antd/src/components/description/src/description.vue ================================================ ================================================ FILE: apps/web-antd/src/components/description/src/typing.ts ================================================ import type { DescriptionsProps } from 'ant-design-vue/es/descriptions'; import type { JSX } from 'vue/jsx-runtime'; import type { CSSProperties, VNode } from 'vue'; import type { Recordable } from '@vben/types'; export interface DescItem { labelMinWidth?: number; contentMinWidth?: number; labelStyle?: CSSProperties; field: string; label: JSX.Element | string | VNode; // Merge column span?: number; show?: (...arg: any) => boolean; // render render?: ( val: any, data: Recordable, ) => Element | JSX.Element | number | string | undefined | VNode; } export interface DescriptionProps extends DescriptionsProps { // Whether to include the collapse component useCollapse?: boolean; /** * item configuration * @type DescItem */ schema: DescItem[]; /** * 数据 * @type object */ data: Recordable; } export interface DescInstance { setDescProps(descProps: Partial, delay?: boolean): void; } export type Register = (descInstance: DescInstance) => void; /** * @description: */ export type UseDescReturnType = [Register, DescInstance]; ================================================ FILE: apps/web-antd/src/components/description/src/useDescription.ts ================================================ import type { DescInstance, DescriptionProps, UseDescReturnType, } from './typing'; import { getCurrentInstance, ref, unref } from 'vue'; /** * @deprecated 使用antd原生组件替代 下个版本将会移除 */ export function useDescription( props?: Partial, ): UseDescReturnType { if (!getCurrentInstance()) { throw new Error( 'useDescription() can only be used inside setup() or functional components!', ); } const desc = ref(null); const loaded = ref(false); function register(instance: DescInstance) { // if (unref(loaded) && import.meta.env.PROD) { // return; // } desc.value = instance; props && instance.setDescProps(props); loaded.value = true; } const methods: DescInstance = { setDescProps: ( descProps: Partial, delay = false, ): void => { if (!delay) { unref(desc)?.setDescProps(descProps); return; } // 奇怪的问题 在modal中需要setTimeout才会生效 setTimeout(() => unref(desc)?.setDescProps(descProps)); }, }; return [register, methods]; } ================================================ FILE: apps/web-antd/src/components/dict/index.ts ================================================ export { tagSelectOptions, tagTypes } from './src/data'; export { default as DictTag } from './src/index.vue'; ================================================ FILE: apps/web-antd/src/components/dict/src/data.tsx ================================================ import type { VNode } from 'vue'; import { Tag } from 'ant-design-vue'; interface TagType { [key: string]: { color: string; label: string }; } export const tagTypes: TagType = { cyan: { color: 'cyan', label: 'cyan' }, danger: { color: 'error', label: '危险(danger)' }, /** 由于和elementUI不同 用于替换颜色 */ default: { color: 'default', label: '默认(default)' }, green: { color: 'green', label: 'green' }, info: { color: 'default', label: '信息(info)' }, orange: { color: 'orange', label: 'orange' }, /** 自定义预设 color可以为16进制颜色 */ pink: { color: 'pink', label: 'pink' }, primary: { color: 'processing', label: '主要(primary)' }, purple: { color: 'purple', label: 'purple' }, red: { color: 'red', label: 'red' }, success: { color: 'success', label: '成功(success)' }, warning: { color: 'warning', label: '警告(warning)' }, }; // 字典选择使用 { label: string; value: string }[] interface Options { label: string | VNode; value: string; } export function tagSelectOptions() { const selectArray: Options[] = []; Object.keys(tagTypes).forEach((key) => { if (!tagTypes[key]) return; const label = tagTypes[key].label; const color = tagTypes[key].color; selectArray.push({ label: {label}, value: key, }); }); return selectArray; } ================================================ FILE: apps/web-antd/src/components/dict/src/index.vue ================================================ ================================================ FILE: apps/web-antd/src/components/dict/src/type.d.ts ================================================ /** * fallback的渲染 * 可返回 字符串/Vnode */ export type DictFallback = | ((current: number | string) => string | VNode) | string; ================================================ FILE: apps/web-antd/src/components/global/button.ts ================================================ import { defineComponent, h } from 'vue'; import { Button } from 'ant-design-vue'; import buttonProps from 'ant-design-vue/es/button/buttonTypes'; import { omit } from 'lodash-es'; /** * 表格操作列按钮专用 */ export const GhostButton = defineComponent({ name: 'GhostButton', props: omit(buttonProps(), ['type', 'ghost', 'size']), setup(props, { attrs, slots }) { return () => h( Button, { ...props, ...attrs, type: 'primary', ghost: true, size: 'small' }, slots, ); }, }); ================================================ FILE: apps/web-antd/src/components/global/index.ts ================================================ import type { App } from 'vue'; import { Button as AButton } from 'ant-design-vue'; import { GhostButton } from './button'; /** * 全局组件注册 */ export function setupGlobalComponent(app: App) { app.use(AButton); // 表格操作列专用按钮 app.component('GhostButton', GhostButton); } ================================================ FILE: apps/web-antd/src/components/global/slot.ts ================================================ import { defineComponent, h } from 'vue'; /** * 使用默认插槽来自定义组件 * 给vbenForm的components使用 */ export const DefaultSlot = defineComponent({ name: 'DefaultSlot', inheritAttrs: false, props: { /** * 绑定到根节点的div上的属性 */ rootDivAttrs: { type: Object, default: () => ({}), }, }, render() { /** * 获取属性 传递给作用域插槽供外部使用 */ const attrs = this.$attrs; return h('div', { ...this.rootDivAttrs }, this.$slots.default?.(attrs)); }, }); ================================================ FILE: apps/web-antd/src/components/table/index.ts ================================================ export { default as OptionsTag } from './src/options-tag.vue'; export { default as TableSwitch } from './src/table-switch.vue'; ================================================ FILE: apps/web-antd/src/components/table/src/options-tag.vue ================================================ ================================================ FILE: apps/web-antd/src/components/table/src/table-switch.vue ================================================ ================================================ FILE: apps/web-antd/src/components/tenant-toggle/index.ts ================================================ export { default as TenantToggle } from './src/index.vue'; ================================================ FILE: apps/web-antd/src/components/tenant-toggle/src/index.vue ================================================ ================================================ FILE: apps/web-antd/src/components/tinymce/index.ts ================================================ export { default as Tinymce } from './src/editor.vue'; ================================================ FILE: apps/web-antd/src/components/tinymce/src/editor.vue ================================================ ================================================ FILE: apps/web-antd/src/components/tinymce/src/helper.ts ================================================ import { ossInfo } from '#/api/system/oss'; /** * 富文本内容中图片ossId转换 确保每次链接都是最新获取的(对于私有桶情况) * * 当然你可以使用后端来解析dom替换 达到相同的效果 就不用前端调用了 * 使用方法: 在赋值前调用此方法 contentWithOssIdTransform(content); 转换一次再赋值 * @param content 富文本内容 * @returns string */ export async function contentWithOssIdTransform(content: string) { if (!content) { return null; } const parser = new DOMParser(); const doc = parser.parseFromString(content, 'text/html'); const imgDom = doc.querySelectorAll('img[data-oss-id]'); // 没有包含图片 不做处理 if (imgDom.length === 0) { return content; } // 提取所有data-oss-id属性 作为string[] const ossIds = [...imgDom].map( (img) => (img as HTMLImageElement).dataset.ossId ?? '', ); // 兼容之前的代码 可能并没有储存ossId if (ossIds.length === 0) { return content; } const ossFileList = await ossInfo(ossIds); imgDom.forEach((item) => { const img = item as HTMLImageElement; // 找到对应的 替换 const src = ossFileList.find((file) => file.ossId === img.dataset.ossId)?.url ?? // 未找到 取原先自己的src img.src; img.setAttribute('src', src); }); // 获取dom string return doc.body.innerHTML; } ================================================ FILE: apps/web-antd/src/components/tinymce/src/tinymce.ts ================================================ // Any plugins you want to setting has to be imported // Detail plugins list see https://www.tinymce.com/docs/plugins/ // Custom builds see https://www.tinymce.com/download/custom-builds/ // colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration // quickbars 快捷栏 export const plugins = 'preview importcss searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help charmap emoticons accordion'; export const toolbar = 'undo redo | accordion accordionremove | blocks fontfamily fontsize | bold italic underline strikethrough | align numlist bullist | link image | table media | lineheight outdent indent| forecolor backcolor removeformat | charmap emoticons | code fullscreen preview | save print | pagebreak anchor codesample | ltr rtl'; ================================================ FILE: apps/web-antd/src/components/tree/index.ts ================================================ export { default as MenuSelectTable } from './src/menu-select-table.vue'; export { default as TreeSelectPanel } from './src/tree-select-panel.vue'; ================================================ FILE: apps/web-antd/src/components/tree/src/data.tsx ================================================ import type { VxeGridProps } from '#/adapter/vxe-table'; import type { ID } from '#/api/common'; import type { MenuOption } from '#/api/system/menu/model'; import { h, markRaw } from 'vue'; import { FolderIcon, MenuIcon, OkButtonIcon, VbenIcon } from '@vben/icons'; export interface Permission { checked: boolean; id: ID; label: string; } export interface MenuPermissionOption extends MenuOption { permissions: Permission[]; } const menuTypes = { C: { icon: markRaw(MenuIcon), value: '菜单' }, F: { icon: markRaw(OkButtonIcon), value: '按钮' }, M: { icon: markRaw(FolderIcon), value: '目录' }, }; export const nodeOptions = [ { label: '节点关联', value: true }, { label: '节点独立', value: false }, ]; export const columns: VxeGridProps['columns'] = [ { type: 'checkbox', title: '菜单名称', field: 'label', treeNode: true, headerAlign: 'left', align: 'left', width: 230, }, { title: '图标', field: 'icon', width: 80, slots: { default: ({ row }) => { if (row?.icon === '#') { return ''; } return ( ); }, }, }, { title: '类型', field: 'menuType', width: 80, slots: { default: ({ row }) => { const current = menuTypes[row.menuType as 'C' | 'F' | 'M']; if (!current) { return '未知'; } return ( {h(current.icon, { class: 'size-[18px]' })} {current.value} ); }, }, }, { title: '权限标识', field: 'permissions', headerAlign: 'left', align: 'left', slots: { default: 'permissions', }, }, ]; ================================================ FILE: apps/web-antd/src/components/tree/src/helper.tsx ================================================ import type { MenuPermissionOption } from './data'; import type { useVbenVxeGrid } from '#/adapter/vxe-table'; import type { MenuOption } from '#/api/system/menu/model'; import { eachTree, treeToList } from '@vben/utils'; import { notification } from 'ant-design-vue'; import { difference, isEmpty, isUndefined } from 'lodash-es'; /** * 权限列设置是否全选 * @param record 行记录 * @param checked 是否选中 */ export function setPermissionsChecked( record: MenuPermissionOption, checked: boolean, ) { if (record?.permissions?.length > 0) { // 全部设置为选中 record.permissions.forEach((permission) => { permission.checked = checked; }); } } /** * 设置当前行 & 所有子节点选中状态 * @param record 行 * @param checked 是否选中 */ export function rowAndChildrenChecked( record: MenuPermissionOption, checked: boolean, ) { // 当前行选中 setPermissionsChecked(record, checked); // 所有子节点选中 record?.children?.forEach?.((permission) => { rowAndChildrenChecked(permission as MenuPermissionOption, checked); }); } /** * void方法 会直接修改原始数据 * 将树结构转为 tree+permissions结构 * @param menus 后台返回的menu */ export function menusWithPermissions(menus: MenuOption[]) { eachTree(menus, (item: MenuPermissionOption) => { validateMenuTree(item); if (item.children && item.children.length > 0) { /** * 所有为按钮的节点提取出来 * 需要注意 这里需要过滤目录下直接是按钮的情况item.menuType !== 'M' * 将按钮往children添加而非加到permissions */ const permissions = item.children.filter( (child: MenuOption) => child.menuType === 'F' && item.menuType !== 'M', ); // 取差集 const diffCollection = difference(item.children, permissions); // 更新后的children 即去除按钮 item.children = diffCollection; // permissions作为字段添加到item const permissionsArr = permissions.map((permission) => { return { id: permission.id, label: permission.label, checked: false, }; }); item.permissions = permissionsArr; } }); } /** * 设置表格选中 * @param checkedKeys 选中的keys * @param menus 菜单 转换后的菜单 * @param tableApi api * @param association 是否节点关联 */ export function setTableChecked( checkedKeys: (number | string)[], menus: MenuPermissionOption[], tableApi: ReturnType['1'], association: boolean, ) { // tree转list const menuList: MenuPermissionOption[] = treeToList(menus); // 拿到勾选的行数据 let checkedRows = menuList.filter((item) => checkedKeys.includes(item.id)); /** * 节点独立切换到节点关联 只需要最末尾的数据 即children为空 */ if (!association) { checkedRows = checkedRows.filter( (item) => isUndefined(item.children) || isEmpty(item.children), ); } // 设置行选中 & permissions选中 checkedRows.forEach((item) => { tableApi.grid.setCheckboxRow(item, true); if (item?.permissions?.length > 0) { item.permissions.forEach((permission) => { if (checkedKeys.includes(permission.id)) { permission.checked = true; } }); } }); /** * 节点独立切换到节点关联 * 勾选后还需要过滤权限没有任何勾选的情况 这时候取消行的勾选 */ if (!association) { const emptyRows = checkedRows.filter((item) => { if (isUndefined(item.permissions) || isEmpty(item.permissions)) { return false; } return item.permissions.every( (permission) => permission.checked === false, ); }); // 设置为不选中 tableApi.grid.setCheckboxRow(emptyRows, false); } } /** * 校验是否符合规范 给出warning提示 * * 不符合规范 * 比如: 菜单下放目录 菜单下放菜单 * 比如: 按钮下放目录 按钮下放菜单 按钮下放按钮 * @param menu menu */ function validateMenuTree(menu: MenuOption) { /** * C: { icon: markRaw(MenuIcon), value: '菜单' }, F: { icon: markRaw(OkButtonIcon), value: '按钮' }, M: { icon: markRaw(FolderIcon), value: '目录' }, */ // 菜单下不能放目录/菜单 if (menu.menuType === 'C') { menu.children?.forEach?.((item) => { if (['C', 'M'].includes(item.menuType)) { const description = `错误用法: [${menu.label} - 菜单]下不能放 目录/菜单 -> [${item.label}]`; console.warn(description); notification.warning({ message: '提示', description, duration: 0, }); } }); } // 按钮为最末级 不能再放置 if (menu.menuType === 'F') { /** * 其实可以直接判断length 这里为了更准确知道label 采用遍历的形式 */ menu.children?.forEach?.((item) => { if (['C', 'F', 'M'].includes(item.menuType)) { const description = `错误用法: [${menu.label} - 按钮]下不能放置'目录/菜单/按钮' -> [${item.label}]`; console.warn(description); notification.warning({ message: '提示', description, duration: 0, }); } }); } } ================================================ FILE: apps/web-antd/src/components/tree/src/hook.tsx ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { TourProps } from 'ant-design-vue'; import { defineComponent, ref } from 'vue'; import { useLocalStorage } from '@vueuse/core'; import { Tour } from 'ant-design-vue'; /** * 全屏引导 * @returns value */ export function useFullScreenGuide() { const open = ref(false); /** * 是否已读 只显示一次 */ const read = useLocalStorage('menu_select_fullscreen_read', false); function openGuide() { if (!read.value) { open.value = true; } } function closeGuide() { open.value = false; read.value = true; } const steps: TourProps['steps'] = [ { title: '提示', description: '点击这里可以全屏', target: () => document.querySelector( 'div#menu-select-table .vxe-tools--operate > button[title="全屏"]', )!, }, ]; const FullScreenGuide = defineComponent({ name: 'FullScreenGuide', inheritAttrs: false, setup() { return () => ( ); }, }); return { FullScreenGuide, openGuide, closeGuide, }; } ================================================ FILE: apps/web-antd/src/components/tree/src/menu-select-table.vue ================================================ ================================================ FILE: apps/web-antd/src/components/tree/src/tree-select-panel.vue ================================================ ================================================ FILE: apps/web-antd/src/components/upload/index.ts ================================================ export { default as FileUpload } from './src/file-upload.vue'; export { default as ImageUpload } from './src/image-upload.vue'; ================================================ FILE: apps/web-antd/src/components/upload/src/file-upload.vue ================================================ ================================================ FILE: apps/web-antd/src/components/upload/src/helper.ts ================================================ import type { UploadFile } from 'ant-design-vue'; /** * 默认支持上传的图片文件类型 */ export const defaultImageAcceptExts = [ '.jpg', '.jpeg', '.png', '.gif', '.webp', ]; /** * 默认支持上传的文件类型 */ export const defaultFileAcceptExts = ['.xlsx', '.csv', '.docx', '.pdf']; /** * 文件(非图片)的默认预览逻辑 * 默认: window.open打开 交给浏览器接管 * @param file file */ export function defaultFilePreview(file: UploadFile) { if (file?.url) { window.open(file.url); } } ================================================ FILE: apps/web-antd/src/components/upload/src/hook.ts ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { UploadChangeParam, UploadFile } from 'ant-design-vue'; import type { FileType } from 'ant-design-vue/es/upload/interface'; import type { RcFile, UploadRequestOption, } from 'ant-design-vue/es/vc-upload/interface'; import type { ModelRef } from 'vue'; import type { BaseUploadProps, CustomGetter, UploadEmits, UploadType, } from './props'; import type { AxiosProgressEvent, UploadResult } from '#/api'; import type { OssFile } from '#/api/system/oss/model'; import { computed, onUnmounted, ref, watch } from 'vue'; import { $t } from '@vben/locales'; import { message, Modal } from 'ant-design-vue'; import { isFunction, isString } from 'lodash-es'; import { ossInfo } from '#/api/system/oss'; /** * 图片预览hook * @returns 预览 */ export function useImagePreview() { /** * 获取base64字符串 * @param file 文件 * @returns base64字符串 */ function getBase64(file: File) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(file); reader.addEventListener('load', () => resolve(reader.result)); reader.addEventListener('error', (error) => reject(error)); }); } // Modal可见 const previewVisible = ref(false); // 预览的图片 url/base64 const previewImage = ref(''); // 预览的图片名称 const previewTitle = ref(''); function handleCancel() { previewVisible.value = false; previewTitle.value = ''; } async function handlePreview(file: UploadFile) { if (!file) { return; } // 文件预览 取base64 if (!file.url && !file.preview && file.originFileObj) { file.preview = (await getBase64(file.originFileObj)) as string; } // 这里不可能为空 const url = file.url ?? ''; previewImage.value = url || file.preview || ''; previewVisible.value = true; previewTitle.value = file.name || url.slice(Math.max(0, url.lastIndexOf('/') + 1)); } return { previewVisible, previewImage, previewTitle, handleCancel, handlePreview, }; } /** * 图片上传和文件上传的通用hook * @param props 组件props * @param emit 事件 * @param bindValue 双向绑定的idList * @param uploadType 区分是文件还是图片上传 * @returns hook */ export function useUpload( props: Readonly, emit: UploadEmits, bindValue: ModelRef, uploadType: UploadType, ) { // 组件内部维护fileList const innerFileList = ref([]); const acceptStr = computed(() => { // string类型 if (isString(props.acceptFormat)) { return props.acceptFormat; } // 函数类型 if (isFunction(props.acceptFormat)) { return props.acceptFormat(props.accept!); } // 默认 会对拓展名做处理 return props.accept ?.split(',') .map((item) => { if (item.startsWith('.')) { return item.slice(1); } return item; }) .join(', '); }); /** * 自定义文件显示名称 需要区分不同的接口 * @param cb callback * @returns 文件名 */ function transformFilename(cb: Parameters>[0]) { if (isFunction(props.customFilename)) { return props.customFilename(cb); } // info接口 if (cb.type === 'info') { return cb.response.originalName; } // 上传接口 return cb.response.fileName; } /** * 自定义缩略图 需要区分不同的接口 * @param cb callback * @returns 缩略图地址 */ function transformThumbUrl(cb: Parameters>[0]) { if (isFunction(props.customThumbUrl)) { return props.customThumbUrl(cb); } // image 默认返回图片链接 if (uploadType === 'image') { // info接口 if (cb.type === 'info') { return cb.response.url; } // 上传接口 return cb.response.url; } // 文件默认返回空 走antd默认的预览图逻辑 return undefined; } // 用来标识是否为上传 这样在watch内部不需要请求api let isUpload = false; function handleChange(info: UploadChangeParam) { /** * 移除当前文件 * @param currentFile 当前文件 * @param currentFileList 当前所有文件list */ function removeCurrentFile( currentFile: UploadChangeParam['file'], currentFileList: UploadChangeParam['fileList'], ) { if (props.removeOnError) { currentFileList.splice(currentFileList.indexOf(currentFile), 1); } else { currentFile.status = 'error'; } } const { file: currentFile, fileList } = info; switch (currentFile.status) { // 上传成功 只是判断httpStatus 200 需要手动判断业务code case 'done': { if (!currentFile.response) { return; } // 获取返回结果 为customRequest的reslove参数 // 只有success才会走到这里 const { ossId, url } = currentFile.response as UploadResult; currentFile.url = url; currentFile.uid = ossId; const cb = { type: 'upload', response: currentFile.response as UploadResult, } as const; currentFile.fileName = transformFilename(cb); currentFile.name = transformFilename(cb); currentFile.thumbUrl = transformThumbUrl(cb); // 标记为上传 watch根据值做处理 isUpload = true; // ossID添加 单个文件会被当做string if (props.maxCount === 1) { bindValue.value = ossId; } else { // 给默认值 if (!Array.isArray(bindValue.value)) { bindValue.value = []; } // 直接使用.value无法触发useForm的更新(原生是正常的) 需要修改地址 bindValue.value = [...bindValue.value, ossId]; } break; } // 上传失败 网络原因导致httpStatus 不等于200 case 'error': { removeCurrentFile(currentFile, fileList); } } emit('change', info); } function handleRemove(currentFile: UploadFile) { function remove() { // fileList会自行处理删除 这里只需要处理ossId if (props.maxCount === 1) { bindValue.value = ''; } else { (bindValue.value as string[]).splice( bindValue.value.indexOf(currentFile.uid), 1, ); } // 触发remove事件 emit('remove', currentFile); } if (!props.removeConfirm) { remove(); return true; } return new Promise((resolve) => { Modal.confirm({ title: $t('pages.common.tip'), content: $t('component.upload.confirmDelete', [currentFile.name]), okButtonProps: { danger: true }, centered: true, onOk() { resolve(true); remove(); }, onCancel() { resolve(false); }, }); }); } /** * 上传前检测文件大小 * 拖拽时候前置会有浏览器自身的accept校验 校验失败不会执行此方法 * @param file file * @returns file | false */ function beforeUpload(file: FileType) { const isLtMax = file.size / 1024 / 1024 < props.maxSize!; if (!isLtMax) { message.error($t('component.upload.maxSize', [props.maxSize])); return false; } // 大坑 Safari不支持file-type库 去除文件类型的校验 return file; } const uploadAbort = new AbortController(); /** * 自定义上传实现 * @param info */ async function customRequest(info: UploadRequestOption) { const { api } = props; if (!isFunction(api)) { console.warn('upload api must exist and be a function'); return; } try { // 进度条事件 const progressEvent: AxiosProgressEvent = (e) => { const percent = Math.trunc((e.loaded / e.total!) * 100); info.onProgress!({ percent }); }; const res = await api(info.file as File, { onUploadProgress: progressEvent, signal: uploadAbort.signal, otherData: props?.data, }); info.onSuccess!(res); if (props.showSuccessMsg) { message.success($t('component.upload.uploadSuccess')); } emit('success', info.file as RcFile, res); } catch (error: any) { console.error(error); info.onError!(error); } } onUnmounted(() => { props.abortOnUnmounted && uploadAbort.abort(); }); /** * 这里默认只监听list地址变化 即重新赋值才会触发watch * immediate用于初始化触发 */ watch( () => bindValue.value, async (value) => { if (value.length === 0) { // 清空绑定值时,同时清空innerFileList,避免外部使用时还能读取到 innerFileList.value = []; return; } // 上传完毕 不需要调用获取信息接口 if (isUpload) { // 清理 使下一次状态可用 isUpload = false; return; } // 处理URL直接传入的情况(例如编辑时从数据库获取的完整URL) // 检查value是否是URL格式 const isUrl = isString(value) && (value.startsWith('http://') || value.startsWith('https://')); if (isUrl) { // 直接使用URL,不调用ossInfo const fileitem: UploadFile = { uid: value, // 使用URL作为uid name: value.split('/').pop() || value, // 从URL提取文件名 fileName: value.split('/').pop() || value, url: value, thumbUrl: value, status: 'done', }; innerFileList.value = [fileitem]; return; } const resp = await ossInfo(value); function transformFile(info: OssFile) { const cb = { type: 'info', response: info } as const; const fileitem: UploadFile = { uid: info.ossId, name: transformFilename(cb), fileName: transformFilename(cb), url: info.url, thumbUrl: transformThumbUrl(cb), status: 'done', }; return fileitem; } const transformOptions = resp.map((item) => transformFile(item)); innerFileList.value = transformOptions; // 单文件 丢弃策略 if (props.maxCount === 1 && resp.length === 0 && !props.keepMissingId) { bindValue.value = ''; return; } // 多文件 // 单文件查到了也会走这里的逻辑 filter会报错 需要maxCount判断处理 if ( resp.length !== value.length && !props.keepMissingId && props.maxCount !== 1 ) { // 给默认值 if (!Array.isArray(bindValue.value)) { bindValue.value = []; } bindValue.value = bindValue.value.filter((ossId) => resp.map((res) => res.ossId).includes(ossId), ); } }, { immediate: true }, ); return { handleChange, handleRemove, beforeUpload, customRequest, innerFileList, acceptStr, }; } ================================================ FILE: apps/web-antd/src/components/upload/src/image-upload.vue ================================================ ================================================ FILE: apps/web-antd/src/components/upload/src/note.md ================================================ Safari在执行到beforeUpload方法 有两种情况 1. 不继续执行 也无法上传(没有调用上传) 2. 报错 Unhandled Promise Rejection: TypeError: ReadableStreamBYOBReader needs a ReadableByteStreamController https://github.com/oven-sh/bun/issues/12908#issuecomment-2490151231 刚开始以为是异步的问题 由于`file-type`调用了异步方法 调试也是在这里没有后续打印了 使用别的异步代码测试结果是正常上传的 ```js return new Promise((resolve) => setTimeout(() => resolve(file), 2000), ); ``` 根本原因在于`file-typ`库的`fileTypeFromBlob`方法不支持Safari 去掉可以正常上传 safari不支持`ReadableStreamBYOBReader`api 详见: https://github.com/sindresorhus/file-type/issues/690 ================================================ FILE: apps/web-antd/src/components/upload/src/props.d.ts ================================================ import type { UploadFile } from 'ant-design-vue'; import type { RcFile } from 'ant-design-vue/es/vc-upload/interface'; import type { UploadApi, UploadResult } from '#/api'; import type { OssFile } from '#/api/system/oss/model'; import { UploadChangeParam } from 'ant-design-vue'; export type UploadType = 'file' | 'image'; /** * 自定义返回文件名/缩略图使用 泛型控制返回是否必填 * type 为不同的接口返回值 需要自行if判断 */ export type CustomGetter = ( cb: | { response: OssFile; type: 'info' } | { response: UploadResult; type: 'upload' }, ) => T extends undefined ? string | undefined : string; export interface BaseUploadProps { /** * 上传接口 */ api?: UploadApi; /** * 文件上传失败 是否从展示列表中删除 * @default true */ removeOnError?: boolean; /** * 上传成功 是否展示提示信息 * @default true */ showSuccessMsg?: boolean; /** * 删除文件前是否需要确认 * @default false */ removeConfirm?: boolean; /** * 同antdv参数 */ accept?: string; /** * 你可能使用的是application/pdf这种mime类型, 但是这样用户可能看不懂, 在这里自定义逻辑 * @default 原始accept */ acceptFormat?: ((accept: string) => string) | string; /** * 附带的请求参数 */ data?: any; /** * 最大上传图片数量 * maxCount为1时 会被绑定为string而非string[] * @default 1 */ maxCount?: number; /** * 文件最大 单位M * @default 5 */ maxSize?: number; /** * 是否禁用 * @default false */ disabled?: boolean; /** * 是否显示文案 请上传不超过... * @default true */ helpMessage?: boolean; /** * 是否支持多选文件,ie10+ 支持。开启后按住 ctrl 可选择多个文件。 * @default false */ multiple?: boolean; /** * 是否支持上传文件夹 * @default false */ directory?: boolean; /** * 是否支持拖拽上传 * @default false */ enableDragUpload?: boolean; /** * 当ossId查询不到文件信息时 比如被删除了 * 是否保留列表对应的ossId 默认不保留 * @default false */ keepMissingId?: boolean; /** * 自定义文件/图片预览逻辑 比如: 你可以改为下载 * 图片上传默认为预览 * 文件上传默认为window.open * @param file file */ preview?: (file: UploadFile) => Promise | void; /** * 是否在组件Unmounted时取消上传 * @default true */ abortOnUnmounted?: boolean; /** * 自定义文件名 需要区分两个接口的返回值 */ customFilename?: CustomGetter; /** * 自定义缩略图 需要区分两个接口的返回值 */ customThumbUrl?: CustomGetter; } export interface UploadEmits { (e: 'success', file: RcFile, response: UploadResult): void; (e: 'remove', file: UploadFile): void; (e: 'change', info: UploadChangeParam): void; } ================================================ FILE: apps/web-antd/src/components/upload-old/index.ts ================================================ /** * @description: 旧版文件上传组件 使用FileUpload代替 */ export { default as FileUploadOld } from './src/file-upload.vue'; /** * @description: 旧版图片上传组件 使用ImageUpload代替 */ export { default as ImageUploadOld } from './src/image-upload.vue'; ================================================ FILE: apps/web-antd/src/components/upload-old/src/file-upload.vue ================================================ ================================================ FILE: apps/web-antd/src/components/upload-old/src/helper.ts ================================================ import { fileTypeFromBlob } from '@vben/utils'; /** * 不支持txt文件 @see https://github.com/sindresorhus/file-type/issues/55 * 需要自行修改 * @param file file对象 * @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*) * @returns 是否通过文件类型校验 */ export async function checkFileType(file: File, accepts: string[]) { if (!accepts || accepts?.length === 0) { return true; } console.log(file); const fileType = await fileTypeFromBlob(file); if (!fileType) { console.error('无法获取文件类型'); return false; } console.log('文件类型', fileType); // 是否文件拓展名/文件头任意有一个匹配 return accepts.includes(fileType.ext) || accepts.includes(fileType.mime); } /** * 默认图片类型 */ export const defaultImageAccept = ['jpg', 'jpeg', 'png', 'gif', 'webp']; /** * 判断文件类型是否符合要求 * @param file file对象 * @param accepts 文件类型数组 包括拓展名(不带点) 文件头(image/png等 不包括泛写法即image/*) * @returns 是否通过文件类型校验 */ export async function checkImageFileType(file: File, accepts: string[]) { // 空的accepts 使用默认规则 if (!accepts || accepts.length === 0) { accepts = defaultImageAccept; } const fileType = await fileTypeFromBlob(file); if (!fileType) { console.error('无法获取文件类型'); return false; } console.log('文件类型', fileType); // 是否文件拓展名/文件头任意有一个匹配 if (accepts.includes(fileType.ext) || accepts.includes(fileType.mime)) { return true; } return false; } ================================================ FILE: apps/web-antd/src/components/upload-old/src/image-upload.vue ================================================ ================================================ FILE: apps/web-antd/src/components/upload-old/src/typing.ts ================================================ import type { Recordable } from '@vben/types'; export enum UploadResultStatus { DONE = 'done', ERROR = 'error', SUCCESS = 'success', UPLOADING = 'uploading', } export interface FileItem { thumbUrl?: string; name: string; size: number | string; type?: string; percent: number; file: File; status?: UploadResultStatus; response?: Recordable | { fileName: string; ossId: string; url: string }; uuid: string; } export interface Wrapper { record: FileItem; uidKey: string; valueKey: string; } export interface BaseFileItem { uid: number | string; url: string; name?: string; } export interface PreviewFileItem { url: string; name: string; type: string; } ================================================ FILE: apps/web-antd/src/components/upload-old/src/use-upload.ts ================================================ import type { Ref } from 'vue'; import { computed, unref } from 'vue'; import { $t } from '@vben/locales'; export function useUploadType({ acceptRef, helpTextRef, maxNumberRef, maxSizeRef, }: { acceptRef: Ref; helpTextRef: Ref; maxNumberRef: Ref; maxSizeRef: Ref; }) { // 文件类型限制 const getAccept = computed(() => { const accept = unref(acceptRef); if (accept && accept.length > 0) { return accept; } return []; }); const getStringAccept = computed(() => { return unref(getAccept) .map((item) => { return item.indexOf('/') > 0 || item.startsWith('.') ? item : `.${item}`; }) .join(','); }); // 支持jpg、jpeg、png格式,不超过2M,最多可选择10张图片,。 const getHelpText = computed(() => { const helpText = unref(helpTextRef); if (helpText) { return helpText; } const helpTexts: string[] = []; const accept = unref(acceptRef); if (accept.length > 0) { helpTexts.push($t('component.upload.accept', [accept.join(',')])); } const maxSize = unref(maxSizeRef); if (maxSize) { helpTexts.push($t('component.upload.maxSize', [maxSize])); } const maxNumber = unref(maxNumberRef); if (maxNumber && maxNumber !== Infinity) { helpTexts.push($t('component.upload.maxNumber', [maxNumber])); } return helpTexts.join(','); }); return { getAccept, getStringAccept, getHelpText }; } ================================================ FILE: apps/web-antd/src/layouts/auth.vue ================================================ ================================================ FILE: apps/web-antd/src/layouts/basic.vue ================================================ ================================================ FILE: apps/web-antd/src/layouts/index.ts ================================================ const BasicLayout = () => import('./basic.vue'); const AuthPageLayout = () => import('./auth.vue'); const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView); export { AuthPageLayout, BasicLayout, IFrameView }; ================================================ FILE: apps/web-antd/src/locales/README.md ================================================ # locale 每个app使用的国际化可能不同,这里用于扩展国际化的功能,例如扩展 dayjs、antd组件库的多语言切换,以及app本身的国际化文件。 ================================================ FILE: apps/web-antd/src/locales/index.ts ================================================ import type { Locale } from 'ant-design-vue/es/locale'; import type { App } from 'vue'; import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; import { ref } from 'vue'; import { $t, setupI18n as coreSetup, loadLocalesMapFromDir, } from '@vben/locales'; import { preferences } from '@vben/preferences'; import antdEnLocale from 'ant-design-vue/es/locale/en_US'; import antdDefaultLocale from 'ant-design-vue/es/locale/zh_CN'; import dayjs from 'dayjs'; const antdLocale = ref(antdDefaultLocale); const modules = import.meta.glob('./langs/**/*.json'); const localesMap = loadLocalesMapFromDir( /\.\/langs\/([^/]+)\/(.*)\.json$/, modules, ); /** * 加载应用特有的语言包 * 这里也可以改造为从服务端获取翻译数据 * @param lang */ async function loadMessages(lang: SupportedLanguagesType) { const [appLocaleMessages] = await Promise.all([ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion localesMap[lang]!(), loadThirdPartyMessage(lang), ]); return appLocaleMessages.default; } /** * 加载第三方组件库的语言包 * @param lang */ async function loadThirdPartyMessage(lang: SupportedLanguagesType) { await Promise.all([loadAntdLocale(lang), loadDayjsLocale(lang)]); } /** * 加载dayjs的语言包 * @param lang */ async function loadDayjsLocale(lang: SupportedLanguagesType) { let locale; switch (lang) { case 'en-US': { locale = await import('dayjs/locale/en'); break; } case 'zh-CN': { locale = await import('dayjs/locale/zh-cn'); break; } // 默认使用英语 default: { locale = await import('dayjs/locale/en'); } } if (locale) { dayjs.locale(locale); } else { console.error(`Failed to load dayjs locale for ${lang}`); } } /** * 加载antd的语言包 * @param lang */ async function loadAntdLocale(lang: SupportedLanguagesType) { switch (lang) { case 'en-US': { antdLocale.value = antdEnLocale; break; } case 'zh-CN': { antdLocale.value = antdDefaultLocale; break; } } } async function setupI18n(app: App, options: LocaleSetupOptions = {}) { await coreSetup(app, { defaultLocale: preferences.app.locale, loadMessages, missingWarn: !import.meta.env.PROD, ...options, }); } export { $t, antdLocale, setupI18n }; ================================================ FILE: apps/web-antd/src/locales/langs/en-US/component.json ================================================ { "cropper": { "selectImage": "Select Image", "uploadSuccess": "Uploaded success!", "imageTooBig": "Image too big", "modalTitle": "Avatar upload", "okText": "Confirm and upload", "btn_reset": "Reset", "btn_rotate_left": "Counterclockwise rotation", "btn_rotate_right": "Clockwise rotation", "btn_scale_x": "Flip horizontal", "btn_scale_y": "Flip vertical", "btn_zoom_in": "Zoom in", "btn_zoom_out": "Zoom out", "preview": "Preview" }, "tenantToggle": { "placeholder": "Please select a tenant", "switch": "Switch to tenant: ", "reset": "Reset to default tenant" }, "notice": { "title": "Notice", "received": "You have received a new message" }, "upload": { "save": "Save", "upload": "Upload", "imgUpload": "ImageUpload", "uploaded": "Uploaded", "operating": "Operating", "del": "Delete", "download": "download", "saveWarn": "Please wait for the file to upload and save!", "saveError": "There is no file successfully uploaded and cannot be saved!", "preview": "Preview", "choose": "Select the file", "accept": "Support {0} format", "acceptUpload": "Only upload files in {0} format", "maxSize": "A single file does not exceed {0}MB ", "maxSizeMultiple": "Only upload files up to {0}MB!", "maxNumber": "Only upload up to {0} files", "legend": "Legend", "fileName": "File name", "fileSize": "File size", "fileStatue": "File status", "pending": "Pending", "startUpload": "Start upload", "uploadSuccess": "Upload successfully", "uploadError": "Upload failed", "uploading": "Uploading", "uploadWait": "Please wait for the file upload to finish", "reUploadFailed": "Re-upload failed files", "uploadHelpMessage": "Please upload a file in {ext} format that does not exceed {size} .", "unknownFileType": "Unknown file type, unable to upload", "confirmDelete": "Confirm file deletion {0}?", "clickOrDrag": "Click or drag file to this area to upload" } } ================================================ FILE: apps/web-antd/src/locales/langs/en-US/demos.json ================================================ { "title": "Demos", "antd": "Ant Design Vue", "vben": { "title": "Project", "about": "About", "document": "Document", "antdv": "Ant Design Vue Version", "naive-ui": "Naive UI Version", "element-plus": "Element Plus Version" } } ================================================ FILE: apps/web-antd/src/locales/langs/en-US/http.json ================================================ { "apiRequestFailed": "Operation failed", "operationSuccess": "Operation Success", "successTip": "Success Tip", "errorTip": "Error Tip", "loginTimeout": "Login timeout, please log in again" } ================================================ FILE: apps/web-antd/src/locales/langs/en-US/menu.json ================================================ { "root": "Root", "system": { "root": "System", "user": "User", "role": "Role", "menu": "Menu", "dept": "Department", "post": "Post", "dict": "Dictionary", "config": "Parameter Settings", "notice": "Notifications", "log": { "root": "Log", "operation": "Operation Log", "login": "Login Log" }, "oss": "File", "client": "Client" }, "tenant": { "root": "Tenant", "package": "Package" }, "monitor": { "root": "System Monitoring", "online": "Online Users", "cache": "Cache Monitoring", "admin": "Admin Monitoring", "job": "Task Scheduling Center" }, "tool": { "root": "System Tools", "gen": "Code Generation" }, "workflow": { "root": "Workflow", "category": "Process Category", "model": "Model", "define": "Process Definition", "monitor": { "root": "Process Monitoring", "instance": "Process Instance", "todo": "Pending Tasks" }, "form": "Form" }, "task": { "root": "My Tasks", "apply": "My Initiated Tasks", "todo": "My Pending Tasks", "done": "My Completed Tasks", "cc": "My CC" } } ================================================ FILE: apps/web-antd/src/locales/langs/en-US/page.json ================================================ { "auth": { "login": "Login", "register": "Register", "codeLogin": "Code Login", "qrcodeLogin": "Qr Code Login", "forgetPassword": "Forget Password", "oauthLogin": "Oauth Login" }, "dashboard": { "title": "Dashboard", "analytics": "Analytics", "workspace": "Workspace" } } ================================================ FILE: apps/web-antd/src/locales/langs/en-US/pages.json ================================================ { "common": { "add": "Add", "edit": "Edit", "delete": "Delete", "more": "More", "search": "Search", "reset": "Reset", "import": "Import", "export": "Export", "expand": "Expand", "collapse": "Collapse", "info": "Info", "clear": "Clear", "unlock": "Unlock", "download": "Download", "sync": "Sync", "refresh": "Refresh", "generate": "Generate", "downloadLoading": "Downloading... Please wait.", "preview": "Preview", "tip": "Tip", "enable": "On", "disable": "Off", "beforeCloseTip": "You have unsaved changes. Are you sure you want to exit?" } } ================================================ FILE: apps/web-antd/src/locales/langs/zh-CN/component.json ================================================ { "cropper": { "selectImage": "选择图片", "uploadSuccess": "上传成功", "imageTooBig": "图片超限", "modalTitle": "头像上传", "okText": "确认并上传", "btn_reset": "重置", "btn_rotate_left": "逆时针旋转", "btn_rotate_right": "顺时针旋转", "btn_scale_x": "水平翻转", "btn_scale_y": "垂直翻转", "btn_zoom_in": "放大", "btn_zoom_out": "缩小", "preview": "预览" }, "tenantToggle": { "placeholder": "选择租户", "switch": "切换当前租户为: ", "reset": "还原为默认租户" }, "notice": { "title": "消息", "received": "收到新消息" }, "upload": { "save": "保存", "upload": "上传", "imgUpload": "图片上传", "uploaded": "已上传", "operating": "操作", "del": "删除", "download": "下载", "saveWarn": "请等待文件上传后,保存!", "saveError": "没有上传成功的文件,无法保存!", "preview": "预览", "choose": "选择文件", "accept": "支持{0}格式", "acceptUpload": "只能上传{0}格式文件", "maxSize": "单个文件不超过{0}MB", "maxSizeMultiple": "只能上传不超过{0}MB的文件!", "maxNumber": "最多只能上传{0}个文件", "legend": "略缩图", "fileName": "文件名", "fileSize": "文件大小", "fileStatue": "状态", "pending": "待上传", "startUpload": "开始上传", "uploadSuccess": "上传成功", "uploadError": "上传失败", "uploading": "上传中", "uploadWait": "请等待文件上传结束后操作", "reUploadFailed": "重新上传失败文件", "uploadHelpMessage": "请上传不超过{size}的{ext}格式文件", "unknownFileType": "未知的文件类型, 无法上传", "confirmDelete": "确认删除文件 {0}?", "clickOrDrag": "点击或拖动文件到这个区域上传" } } ================================================ FILE: apps/web-antd/src/locales/langs/zh-CN/demos.json ================================================ { "title": "演示", "antd": "Ant Design Vue", "vben": { "title": "项目", "about": "关于", "document": "文档", "antdv": "Ant Design Vue 版本", "naive-ui": "Naive UI 版本", "element-plus": "Element Plus 版本" } } ================================================ FILE: apps/web-antd/src/locales/langs/zh-CN/http.json ================================================ { "apiRequestFailed": "请求出错,请稍候重试", "operationSuccess": "操作成功", "successTip": "成功提示", "errorTip": "错误提示", "loginTimeout": "登录超时, 请重新登录" } ================================================ FILE: apps/web-antd/src/locales/langs/zh-CN/menu.json ================================================ { "root": "根目录", "system": { "root": "系统管理", "user": "用户管理", "role": "角色管理", "menu": "菜单管理", "dept": "部门管理", "post": "岗位管理", "dict": "字典管理", "config": "参数设置", "notice": "通知公告", "log": { "root": "日志管理", "operation": "操作日志", "login": "登录日志" }, "oss": "文件管理", "client": "客户端管理" }, "tenant": { "root": "租户管理", "package": "套餐管理" }, "monitor": { "root": "系统监控", "online": "在线用户", "cache": "缓存监控", "admin": "Admin监控", "job": "任务调度中心" }, "tool": { "root": "系统工具", "gen": "代码生成" }, "workflow": { "root": "工作流", "category": "流程分类", "model": "模型管理", "define": "流程定义", "monitor": { "root": "流程监控", "instance": "流程实例", "todo": "待办任务" }, "form": "表单管理" }, "task": { "root": "我的任务", "apply": "我发起的", "todo": "我的待办", "done": "我的已办", "cc": "我的抄送" } } ================================================ FILE: apps/web-antd/src/locales/langs/zh-CN/page.json ================================================ { "auth": { "login": "登录", "register": "注册", "codeLogin": "验证码登录", "qrcodeLogin": "二维码登录", "forgetPassword": "忘记密码", "oauthLogin": "第三方登录" }, "dashboard": { "title": "概览", "analytics": "分析页", "workspace": "工作台" } } ================================================ FILE: apps/web-antd/src/locales/langs/zh-CN/pages.json ================================================ { "common": { "add": "新增", "edit": "编辑", "delete": "删除", "more": "更多", "search": "搜索", "reset": "重置", "import": "导入", "export": "导出", "expand": "展开", "collapse": "收起", "info": "详情", "clear": "清空", "unlock": "解锁", "download": "下载", "sync": "同步", "refresh": "刷新", "generate": "生成", "downloadLoading": "下载中, 请稍后...", "preview": "预览", "tip": "提示", "enable": "启用", "disable": "禁用", "beforeCloseTip": "您有未保存的更改,确认要退出吗?" } } ================================================ FILE: apps/web-antd/src/main.ts ================================================ import { initPreferences } from '@vben/preferences'; import { unmountGlobalLoading } from '@vben/utils'; import { overridesPreferences } from './preferences'; /** * 应用初始化完成之后再进行页面加载渲染 */ async function initApplication() { // name用于指定项目唯一标识 // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 const env = import.meta.env.PROD ? 'prod' : 'dev'; const appVersion = import.meta.env.VITE_APP_VERSION; const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; // app偏好设置初始化 await initPreferences({ namespace, overrides: overridesPreferences, }); // 启动应用并挂载 // vue应用主要逻辑及视图 const { bootstrap } = await import('./bootstrap'); await bootstrap(namespace); // 移除并销毁loading unmountGlobalLoading(); } initApplication(); ================================================ FILE: apps/web-antd/src/packages/workflow-designer/StandaloneWorkflowDesigner.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/CommonNodeHeader.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/RunDetail.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/RuntimeNodes.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/SvgIcon.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/WfVariableSelector.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/edges/CustomEdge.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/edges/CustomEdge2.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/edges/SpecialEdge.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/AnswerNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/ClassifierNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/Dalle3Node.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/DocumentExtractorNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/EndNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/FaqExtractorNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/GoogleNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/HumanFeedbackNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/KeywordExtractorNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/NodeShell.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/StartNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/SwitcherNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/TemplateNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/TestNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/components/nodes/TongyiwanxNode.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/docs/README.md ================================================ # Workflow Designer (Standalone) 可移植的流程编排页面与组件集合,已去除鉴权与项目全局依赖,便于在其他项目中以“分包”方式直接复用。 ## 用法 1. 在你的项目中引入并注册组件: ```vue ``` 2. 必要依赖: - vue 3 - naive-ui(本包 UI 使用) - @vue-flow/core 与 @vue-flow/background 3. 可选:若你已有 `SvgIcon` 组件,可替换 `components/SvgIcon.vue` 的实现。 ## 说明 - 不包含鉴权、用户判断、接口请求等逻辑;对外通过 emits 暴露 `save`、`run` 事件。 - 完整样式与交互来自原工作流模块,做了最小化改造以去除外部依赖。 ## 扩展指引(新增节点/边、接入后端) ### 新增一个节点(Node) 1) 最小化接入 - 在宿主页面(如 `src/views/workflow/index.vue`)把 `{ name, title }` 追加到传入的 `wfComponents` 列表,即可出现在左侧面板并参与拖拽: - 例如:`{ name: 'MyNode', title: '我的节点' }` - 默认配置两种方式(有优先级): - 方式 A(优先级最高):在 `properties/YourNodeProperty.vue` 中导出 - `export function getDefaultNodeConfig(workflow) { return {/* 默认配置 */} }` - 示例:`properties/TestNodeProperty.vue` - 方式 B(集中维护):在 `properties/defaults.ts` 中配置 - `export const propertyDefaultGetters = { yournode: (workflow) => ({ /* 默认配置 */ }) }` - 兜底:若 A/B 都未提供,回退为空对象 `{}`。 - 在 `components/nodes/` 下创建 `MyNode.vue`(可选)。无需手动注册,系统会自动扫描注册: - 文件名 `MyNode.vue` 会被转换成 `mynode` 键名并注入 `nodeTypes`。 - 如果没有提供该文件,会自动回退到通用外观 `NodeShell`。 #### 节点的图标与配色(可选,但强烈建议配置) 左侧组件面板、节点头部会调用两个方法来渲染图标与配色: - `getIconByComponentName(name: string)` → 返回图标名称(如 `carbon:http`) - `getIconClassByComponentName(name: string)` → 返回 Tailwind/类名,用于给图标着色 这两个方法位于 `packages/workflow-designer/utils/workflow-util.ts`,内部根据“节点名的小写”做 `switch` 映射: ```ts // 文件:packages/workflow-designer/utils/workflow-util.ts export function getIconByComponentName(name: string) { switch (name.toLowerCase()) { case 'httprequest': return 'carbon:http' // ... 其他节点 default: return '' // 未配置时不显示图标 } } export function getIconClassByComponentName(name: string) { switch (name.toLowerCase()) { case 'httprequest': return 'text-slate-800' // ... 其他节点 default: return '' // 未配置时使用默认颜色 } } ``` 新增节点时如需自定义图标和颜色,请: 1) 以节点的 `name` 转小写为 `case` 值,分别在上述两个方法中添加一条映射; 2) 图标使用 `Iconify` 风格的标识(本包默认的 `SvgIcon` 支持); 3) 颜色类名可用现有的 Tailwind 色值类(或你项目中可用的类名)。 不配置时不影响功能,只是左侧面板与节点标题处不显示/不着色。 2) 专属属性面板(可选) - 在 `packages/workflow-designer/properties/` 下新增 `YourNodeProperty.vue`,只需编辑 `wfNode.nodeConfig` 相关字段。 - 无需修改任何分支或注册代码:系统会自动扫描 `*NodeProperty.vue` 并按文件名映射到节点类型: - 例如:`HttpRequestNodeProperty.vue` → 键名 `httprequest`,当节点 `name === 'HttpRequest'` 时会自动渲染。 - 若未提供专属面板,自动回退为 `GenericNodeProperty.vue`,按 `nodeConfig` 动态渲染表单。 3) 连接点(Handle)与连线 - 连接点应在各自的节点组件中渲染: - `` - `` - 示例可参考 `components/nodes/AnswerNode.vue`。 ### 新增一种边(Edge) - 在 `components/edges/` 下新增边组件(参考 `SpecialEdge.vue`)。无需手动注册: - 文件名 `MyEdge.vue` 会被转换成 `myedge` 键名并注入 `edgeTypes`。 - 创建/更新/删除边: - `createNewEdge` / `updateEdgeBySourceHandle` / `deleteEdgesBySourceHandle` 可复用,按需调用。 ### 节点中调用后端的规范建议 - 建议由“各节点的 Property 组件”各自发起所需请求;注意使用自己项目的请求实例发起请求,可以保证权限问题。 - 请求结果写入 `wfNode.nodeConfig`,与后端字段一一对应,便于在 `@save` 时一次性提交。 - 鉴权、token 等细节全部由宿主的请求实例处理,子包不关心用户态信息。 ### 命名与约定 - 节点 `name` 使用帕斯卡命名(如 `HttpRequest`),自动注册时将文件 `HttpRequestNode.vue` 转为小写键 `httprequest`。 - 属性面板文件命名:`NodeProperty.vue`(如 `HttpRequestNodeProperty.vue`)。 - `nodeConfig` 字段应尽量与后端模型字段一致;默认值在 `createNewNode` 中集中维护,便于落盘/回显。 - 不在子包中引入项目自有 Store/权限/路由等全局耦合内容;如需共享状态,请通过 `props / provide/inject` 注入。 ### 拖拽面板说明 - 左侧面板通过宿主传入的 `wfComponents` 渲染,拖拽开始事件集中在 `onPaletteDragStart` 方法内: - 通过 `event.dataTransfer.setData('application/vueflow', component.name)` 传递节点类型。 - 放置(drop)逻辑集中在 `onDrop` 方法中,最终由 `createNewNode` 完成数据与画布 UI 的创建。 ### 类型扩展 - 如需新增类型,请在 `types/index.d.ts` 内补充: - `WorkflowComponent` 新类型(必要时) - 对应节点 `nodeConfig` 的 TS 结构(可选,通用面板也支持任意结构) ### 测试与演示 - 打开页面 `src/views/workflow/index.vue`,此页已集成 `StandaloneWorkflowDesigner`。 - 在该文件中: - 将你的节点 `{ name, title }` 追加到 `wfComponents` 数组即可出现在左侧拖拽面板。 - 在本页的 `workflow.nodes` 中按需加入一条样例节点,便于直接查看渲染与属性面板。 - 如有自定义节点/边组件,分别放到 `packages/workflow-designer/components/nodes/*Node.vue` 与 `components/edges/*Edge.vue`,系统会自动扫描注册。 ================================================ FILE: apps/web-antd/src/packages/workflow-designer/docs/WorkflowDesigner树状架构分析.md ================================================ # Workflow Designer 树状架构分析 ## 1) 包结构树 ```text workflow-designer/ ├─ StandaloneWorkflowDesigner.vue # 画布与主交互容器(核心) ├─ index.ts # 默认导出组件 ├─ ARCHITECTURE.md # 架构说明(文字版) ├─ ARCHITECTURE-TREE.md # 架构说明(树状版,本文) │ ├─ components/ # 可视化节点/边与通用组件 │ ├─ nodes/ │ │ ├─ AnswerNode.vue │ │ ├─ ClassifierNode.vue │ │ ├─ Dalle3Node.vue │ │ ├─ DocumentExtractorNode.vue │ │ ├─ EndNode.vue │ │ ├─ FaqExtractorNode.vue │ │ ├─ GoogleNode.vue │ │ ├─ KeywordExtractorNode.vue │ │ ├─ NodeShell.vue # 未提供专属节点时的通用外观 │ │ ├─ StartNode.vue │ │ ├─ SwitcherNode.vue │ │ ├─ TemplateNode.vue │ │ ├─ TestNode.vue │ │ └─ TongyiwanxNode.vue │ ├─ edges/ │ │ ├─ CustomEdge.vue │ │ ├─ CustomEdge2.vue │ │ └─ SpecialEdge.vue │ ├─ CommonNodeHeader.vue │ ├─ RunDetail.vue │ ├─ RuntimeNodes.vue │ ├─ SvgIcon.vue │ └─ WfVariableSelector.vue │ ├─ panels/ │ └─ RightPanel.vue # 右侧属性面板(自动解析专属面板) │ ├─ properties/ # 节点属性面板与默认值 │ ├─ AnswerNodeProperty.vue │ ├─ ClassifierNodeProperty.vue │ ├─ KeywordExtractorNodeProperty.vue │ ├─ StartNodeProperty.vue │ ├─ TestNodeProperty.vue │ ├─ GenericNodeProperty.vue # 通用(动态)属性面板 │ └─ defaults.ts # 默认配置集中映射表 │ ├─ store/ │ └─ index.ts # Pinia Store(可选集成) │ ├─ types/ │ └─ index.d.ts # Workflow/Node/Edge 等类型定义 │ └─ utils/ └─ workflow-util.ts # 节点/边创建、默认值/图标映射、辅助函数 ``` ## 2) 核心关系树(模块依赖与数据流) ```text StandaloneWorkflowDesigner.vue ├─ Props │ ├─ workflow: WorkflowInfo │ ├─ wfComponents: WorkflowComponent[] │ ├─ componentIdMap: Record │ └─ saving?: boolean ├─ Emits │ ├─ save(workflow) │ ├─ run({ workflow }) │ └─ deleteNode(nodeUuid) ├─ 自动注册 │ ├─ nodes: import.meta.glob(components/nodes/*Node.vue) → nodeTypes │ └─ edges: import.meta.glob(components/edges/*Edge.vue) → edgeTypes ├─ 组合 │ └─ RightPanel.vue(属性面板) ├─ 工具依赖 │ └─ utils/workflow-util.ts(createNewNode/createNewEdge/...) └─ 数据层 ├─ 输入:WorkflowInfo(业务层) ├─ 派生:UIWorkflow(画布层) └─ 同步:拖拽/连线/删除/保存 → 写回 WorkflowInfo RightPanel.vue ├─ 自动匹配属性组件:properties/NodeProperty.vue(文件名 → 小写键) └─ 回退:GenericNodeProperty.vue properties/ ├─ NodeProperty.vue(专属面板,可导出 getDefaultNodeConfig) ├─ GenericNodeProperty.vue(通用表单) └─ defaults.ts(propertyDefaultGetters:集中默认值表) utils/workflow-util.ts ├─ emptyWorkflowInfo / emptyWorkflowNode / createUuid ├─ createNewNode / createNewEdge / updateEdgeBySourceHandle / deleteEdgesBySourceHandle ├─ getIconByComponentName / getIconClassByComponentName └─ 默认值决策链:专属导出 > defaults.ts > 空对象 types/index.d.ts ├─ WorkflowInfo(nodes/edges/deleteNodes/deleteEdges/...) ├─ WorkflowNode(wfComponent/workflowComponentId/inputConfig/nodeConfig/...) ├─ WorkflowEdge(uuid/sourceNodeUuid/targetNodeUuid/...) └─ UIWorkflow(画布层 nodes/edges) store/index.ts(可选) ├─ state:工作流列表/运行实例/组件清单/活动态等 ├─ getters:按 uuid/id 查找工作流、起始节点、运行节点 └─ actions:增删改查、运行态维护、路由跳转注入 setWorkflowDesignerNavigator ``` ## 3) 事件与时序树(关键交互) ```text 初始化 └─ onMounted → renderGraph → fitView 拖拽新增节点 └─ onPaletteDragStart → onDrop → createNewNode → 选中节点 → 显示 RightPanel 连线/改线/删线 ├─ onConnect → createNewEdge(同步 UI 与 WorkflowInfo) ├─ updateEdgeBySourceHandle(按源 Handle 改线) └─ onEdgeClick → 删除边(记录 deleteEdges 去重) 节点交互 ├─ onNodeClick(选择并显示属性面板) ├─ onNodeDragStop(写回 positionX/Y) └─ 删除节点 → 级联删除相关边 → 记录 deleteNodes/deleteEdges 保存/运行 ├─ onSave → 同步画布坐标 → 去重 deleteEdges → emits.save(workflow) └─ onRun → emits.run({ workflow }) ``` ## 4) 扩展点树(约定优先) ```text 新增节点(Node) ├─ 宿主传入 wfComponents 追加 { name, title } ├─ 可选:components/nodes/Node.vue(自动注册),否则回退 NodeShell └─ 属性面板:properties/NodeProperty.vue(可导出 getDefaultNodeConfig) 新增边(Edge) └─ components/edges/Edge.vue(自动注册;通过 edgeTypes 使用) 默认值 └─ 优先级:专属面板导出 > properties/defaults.ts > {} 图标/颜色 └─ utils/workflow-util.ts:getIconByComponentName / getIconClassByComponentName 类型扩展 └─ types/index.d.ts:补充 WorkflowComponent、节点 nodeConfig 类型等 ``` ## 5) 对外 API(宿主集成) - Props:`workflow`、`wfComponents`、`componentIdMap`、`saving` - Emits:`save(workflow)`、`run({ workflow })`、`deleteNode(nodeUuid)` - Store(可选):`setWorkflowDesignerNavigator()` 注入路由跳转 ================================================ FILE: apps/web-antd/src/packages/workflow-designer/docs/WorkflowDesigner详细树状架构.md ================================================ # Workflow Designer 详细树状架构(带注释) ## 📁 包结构树(完整目录) ```text workflow-designer/ # 工作流设计器包根目录 │ ├─ 📄 StandaloneWorkflowDesigner.vue # 🎯 核心组件:画布与主交互容器 │ │ # 功能:拖拽画布、节点连线、属性编辑、保存运行 │ │ # 依赖:Vue Flow、Naive UI、内部工具函数 │ │ # 对外:Props(workflow/wfComponents/componentIdMap/saving) + Emits(save/run/deleteNode) │ │ ├─ 📄 index.ts # 📤 包入口:默认导出主组件 │ │ # 导出:StandaloneWorkflowDesigner + 所有类型定义 │ │ ├─ 📄 README.md # 📖 使用说明与扩展指南 │ │ # 内容:基本用法、依赖说明、新增节点/边/属性面板的详细步骤 │ │ ├─ 📄 ARCHITECTURE.md # 🏗️ 架构说明(文字版) │ │ # 内容:组件职责、数据流、集成方式、设计取舍 │ │ ├─ 📄 ARCHITECTURE-TREE.md # 🌳 架构说明(树状版) │ │ # 内容:包结构、核心关系、事件时序、扩展点 │ │ └─ 📄 ARCHITECTURE-DETAILED-TREE.md # 🌲 详细树状架构(本文) │ # 内容:完整目录结构 + 详细功能注释 │ ├─ 📁 components/ # 🧩 可视化组件集合 │ │ # 职责:节点渲染、边渲染、通用UI组件 │ │ │ ├─ 📁 nodes/ # 🔘 节点组件目录 │ │ │ # 约定:Node.vue → 自动注册为 name.toLowerCase() │ │ │ # 回退:未提供时使用 NodeShell.vue 通用外观 │ │ │ │ │ ├─ 📄 AnswerNode.vue # 💬 回答节点:AI对话响应 │ │ │ # 功能:显示AI回答内容,支持流式输出 │ │ │ # 配置:prompt模板、模型选择、输出格式 │ │ │ │ │ ├─ 📄 ClassifierNode.vue # 🏷️ 分类节点:智能分类判断 │ │ │ # 功能:根据输入内容进行多分类判断 │ │ │ # 配置:分类类别、阈值设置、目标节点映射 │ │ │ │ │ ├─ 📄 Dalle3Node.vue # 🎨 图像生成节点:DALL-E 3 │ │ │ # 功能:根据文本描述生成图像 │ │ │ # 配置:提示词、图像尺寸、质量等级 │ │ │ │ │ ├─ 📄 DocumentExtractorNode.vue # 📄 文档提取节点 │ │ │ # 功能:从文档中提取结构化信息 │ │ │ # 配置:提取规则、输出格式、字段映射 │ │ │ │ │ ├─ 📄 EndNode.vue # 🏁 结束节点:流程终点 │ │ │ # 功能:标记工作流结束,收集最终结果 │ │ │ # 配置:结果展示格式、输出变量 │ │ │ │ │ ├─ 📄 FaqExtractorNode.vue # ❓ FAQ提取节点 │ │ │ # 功能:从知识库中提取相关问答对 │ │ │ # 配置:相似度阈值、返回数量、知识库选择 │ │ │ │ │ ├─ 📄 GoogleNode.vue # 🔍 谷歌搜索节点 │ │ │ # 功能:执行谷歌搜索并返回结果 │ │ │ # 配置:搜索关键词、国家地区、语言、结果数量 │ │ │ │ │ ├─ 📄 KeywordExtractorNode.vue # 🔑 关键词提取节点 │ │ │ # 功能:从文本中提取关键词 │ │ │ # 配置:提取算法、关键词数量、过滤条件 │ │ │ │ │ ├─ 📄 NodeShell.vue # 🐚 通用节点外壳(兜底组件) │ │ │ # 功能:当未提供专属节点组件时的通用外观 │ │ │ # 特点:显示节点标题、图标、基本样式 │ │ │ │ │ ├─ 📄 StartNode.vue # 🚀 开始节点:流程起点 │ │ │ # 功能:工作流入口,接收初始输入 │ │ │ # 配置:欢迎语、输入字段定义、变量初始化 │ │ │ │ │ ├─ 📄 SwitcherNode.vue # 🔀 条件分支节点 │ │ │ # 功能:根据条件执行不同分支 │ │ │ # 配置:条件表达式、分支映射、默认分支 │ │ │ │ │ ├─ 📄 TemplateNode.vue # 📝 模板节点:文本模板处理 │ │ │ # 功能:使用模板引擎处理文本内容 │ │ │ # 配置:模板内容、变量替换、输出格式 │ │ │ │ │ ├─ 📄 TestNode.vue # 🧪 测试节点:调试用 │ │ │ # 功能:用于测试和调试工作流 │ │ │ # 配置:测试数据、验证规则、输出格式 │ │ │ │ │ └─ 📄 TongyiwanxNode.vue # 🎨 通义万相节点:阿里图像生成 │ │ # 功能:使用通义万相生成图像 │ │ # 配置:提示词、图像参数、风格设置 │ │ │ ├─ 📁 edges/ # 🔗 边组件目录 │ │ │ # 约定:Edge.vue → 自动注册为 name.toLowerCase() │ │ │ # 用途:定义节点间的连接线样式和行为 │ │ │ │ │ ├─ 📄 CustomEdge.vue # 🎨 自定义边样式1 │ │ │ # 功能:自定义连接线外观和动画效果 │ │ │ # 特点:可配置颜色、粗细、动画类型 │ │ │ │ │ ├─ 📄 CustomEdge2.vue # 🎨 自定义边样式2 │ │ │ # 功能:另一种自定义连接线样式 │ │ │ # 特点:不同的视觉效果和交互行为 │ │ │ │ │ └─ 📄 SpecialEdge.vue # ⭐ 特殊边:默认连接线 │ │ # 功能:工作流中默认使用的连接线样式 │ │ # 特点:带动画效果、支持点击删除 │ │ │ ├─ 📄 CommonNodeHeader.vue # 📋 通用节点头部组件 │ │ # 功能:统一的节点标题栏样式 │ │ # 特点:图标、标题、操作按钮的标准化布局 │ │ │ ├─ 📄 RunDetail.vue # 📊 运行详情组件 │ │ # 功能:显示工作流运行时的详细信息 │ │ # 内容:执行状态、节点输出、错误信息、性能指标 │ │ │ ├─ 📄 RuntimeNodes.vue # ⚡ 运行时节点组件 │ │ # 功能:实时显示工作流执行过程中的节点状态 │ │ # 特点:动态更新、状态指示、进度显示 │ │ │ ├─ 📄 SvgIcon.vue # 🎯 SVG图标组件 │ │ # 功能:统一的图标渲染组件 │ │ # 特点:支持Iconify图标库、可自定义颜色和大小 │ │ │ └─ 📄 WfVariableSelector.vue # 🔧 工作流变量选择器 │ # 功能:选择和管理工作流中的变量 │ # 特点:变量类型识别、作用域管理、引用追踪 │ ├─ 📁 panels/ # 🎛️ 面板组件目录 │ │ # 职责:提供各种功能面板和侧边栏 │ │ │ └─ 📄 RightPanel.vue # 📋 右侧属性面板 │ # 功能:显示和编辑选中节点的属性 │ # 特点:自动匹配专属属性组件、支持节点重命名 │ # 回退:未找到专属面板时使用通用属性表单 │ ├─ 📁 properties/ # ⚙️ 属性配置目录 │ │ # 职责:节点属性面板和默认值配置 │ │ # 约定:NodeProperty.vue → 自动映射到节点类型 │ │ │ ├─ 📄 AnswerNodeProperty.vue # 💬 回答节点属性面板 │ │ # 功能:配置AI回答的参数和选项 │ │ # 配置项:提示词、模型选择、温度、最大长度等 │ │ │ ├─ 📄 ClassifierNodeProperty.vue # 🏷️ 分类节点属性面板 │ │ # 功能:配置分类器的参数和类别 │ │ # 配置项:分类类别、阈值、目标节点映射等 │ │ │ ├─ 📄 KeywordExtractorNodeProperty.vue # 🔑 关键词提取属性面板 │ │ # 功能:配置关键词提取的参数 │ │ # 配置项:提取算法、关键词数量、过滤规则等 │ │ │ ├─ 📄 StartNodeProperty.vue # 🚀 开始节点属性面板 │ │ # 功能:配置工作流入口的参数 │ │ # 配置项:欢迎语、输入字段、变量初始化等 │ │ │ ├─ 📄 TestNodeProperty.vue # 🧪 测试节点属性面板 │ │ # 功能:配置测试节点的参数 │ │ # 配置项:测试数据、验证规则、输出格式等 │ │ │ ├─ 📄 GenericNodeProperty.vue # 🔧 通用属性面板(兜底) │ │ # 功能:当未提供专属属性面板时的通用表单 │ │ # 特点:动态渲染nodeConfig中的所有字段 │ │ │ └─ 📄 defaults.ts # 📋 默认配置集中表 │ # 功能:集中管理所有节点的默认配置 │ # 结构:propertyDefaultGetters[name] = (workflow) => config │ # 优先级:专属面板导出 > 此表 > 空对象 │ ├─ 📁 store/ # 🗄️ 状态管理目录 │ │ # 职责:Pinia store,可选的状态管理 │ │ # 用途:工作流列表、运行实例、跨视图状态 │ │ │ └─ 📄 index.ts # 🏪 Pinia Store定义 │ # 功能:管理工作流和运行时的状态 │ # 状态:工作流列表、运行实例、组件清单、活动状态 │ # 方法:CRUD操作、运行态维护、路由跳转注入 │ ├─ 📁 types/ # 📝 类型定义目录 │ │ # 职责:TypeScript类型定义 │ │ # 内容:工作流、节点、边、UI结构等类型 │ │ │ └─ 📄 index.d.ts # 📋 核心类型定义 │ # 类型:WorkflowInfo、WorkflowNode、WorkflowEdge、WorkflowComponent │ # 用途:提供类型安全和IDE智能提示 │ └─ 📁 utils/ # 🛠️ 工具函数目录 │ # 职责:通用工具函数和业务逻辑 │ # 内容:节点/边操作、默认值处理、图标映射等 │ └─ 📄 workflow-util.ts # 🔧 工作流工具函数 # 功能:节点/边创建、更新、删除、默认值注入 # 工具:图标映射、UUID生成、深拷贝、输入类型转换 # 核心:createNewNode、createNewEdge、getDefaultNodeConfig ``` ## 🔄 数据流与状态管理树 ```text 数据层架构 │ ├─ 📊 业务数据层(WorkflowInfo) │ ├─ nodes: WorkflowNode[] # 节点数据(业务层) │ │ ├─ uuid: string # 节点唯一标识 │ │ ├─ wfComponent: WorkflowComponent # 组件信息 │ │ ├─ workflowComponentId?: number # 后端组件ID(兼容) │ │ ├─ nodeConfig: Record # 节点配置参数 │ │ ├─ inputConfig: NodeIOConfig # 输入配置 │ │ ├─ outputConfig: Record # 输出配置 │ │ └─ positionX/Y: number # 节点坐标 │ │ │ ├─ edges: WorkflowEdge[] # 边数据(业务层) │ │ ├─ uuid: string # 边唯一标识 │ │ ├─ sourceNodeUuid: string # 源节点ID │ │ ├─ targetNodeUuid: string # 目标节点ID │ │ └─ sourceHandle?: string # 源连接点 │ │ │ ├─ deleteNodes: string[] # 待删除节点ID列表 │ └─ deleteEdges: string[] # 待删除边ID列表 │ ├─ 🎨 UI渲染层(UIWorkflow) │ ├─ nodes: VueFlowNode[] # 画布节点(UI层) │ │ ├─ id: string # 对应业务层uuid │ │ ├─ type: string # 节点类型(小写) │ │ ├─ data: WorkflowNode # 业务数据引用 │ │ └─ position: {x, y} # 画布坐标 │ │ │ └─ edges: VueFlowEdge[] # 画布边(UI层) │ ├─ id: string # 对应业务层uuid │ ├─ source: string # 源节点ID │ ├─ target: string # 目标节点ID │ ├─ type: string # 边类型 │ └─ data: WorkflowEdge # 业务数据引用 │ └─ 🔄 同步机制 ├─ 坐标同步:UI层 → 业务层(拖拽结束时) ├─ 数据同步:业务层 → UI层(workflow变化时) ├─ 删除同步:UI操作 → 业务层deleteNodes/deleteEdges └─ 保存同步:业务层 → 宿主应用(emit save事件) ``` ## 🎯 组件注册与映射树 ```text 自动注册机制 │ ├─ 🔘 节点组件注册 │ ├─ 扫描路径:components/nodes/*Node.vue │ ├─ 注册方式:import.meta.glob('./components/nodes/*Node.vue', { eager: true }) │ ├─ 键名转换:Node.vue → name.toLowerCase() │ │ ├─ AnswerNode.vue → 'answer' │ │ ├─ ClassifierNode.vue → 'classifier' │ │ ├─ StartNode.vue → 'start' │ │ └─ ...(其他节点) │ │ │ ├─ 回退机制:未找到专属组件 → NodeShell.vue │ └─ 使用方式:nodeTypes[componentName.toLowerCase()] │ ├─ 🔗 边组件注册 │ ├─ 扫描路径:components/edges/*Edge.vue │ ├─ 注册方式:import.meta.glob('./components/edges/*Edge.vue', { eager: true }) │ ├─ 键名转换:Edge.vue → name.toLowerCase() │ │ ├─ CustomEdge.vue → 'custom' │ │ ├─ CustomEdge2.vue → 'custom2' │ │ └─ SpecialEdge.vue → 'special' │ │ │ └─ 使用方式:edgeTypes[edgeType](创建边时指定type) │ └─ ⚙️ 属性面板注册 ├─ 扫描路径:properties/*NodeProperty.vue ├─ 注册方式:import.meta.glob('../properties/*NodeProperty.vue', { eager: true }) ├─ 键名转换:NodeProperty.vue → name.toLowerCase() │ ├─ AnswerNodeProperty.vue → 'answer' │ ├─ ClassifierNodeProperty.vue → 'classifier' │ └─ ...(其他属性面板) │ ├─ 回退机制:未找到专属面板 → GenericNodeProperty.vue └─ 使用方式:propertyMap[nodeType] || GenericNodeProperty ``` ## 🔧 默认值配置树 ```text 默认值注入机制(三级优先级) │ ├─ 🥇 第一优先级:专属属性面板导出 │ ├─ 位置:properties/NodeProperty.vue │ ├─ 导出:export function getDefaultNodeConfig(workflow: WorkflowInfo) │ ├─ 示例:AnswerNodeProperty.vue 中导出回答节点的默认配置 │ └─ 优势:节点专属、可访问workflow上下文 │ ├─ 🥈 第二优先级:集中配置表 │ ├─ 位置:properties/defaults.ts │ ├─ 结构:propertyDefaultGetters[name] = (workflow) => config │ ├─ 示例: │ │ ├─ start: (wf) => ({ prologue: '欢迎使用工作流' }) │ │ ├─ answer: (wf) => ({ prompt: '', model_name: 'gpt-3.5-turbo' }) │ │ └─ classifier: (wf) => ({ categories: [] }) │ │ │ └─ 优势:集中管理、易于维护 │ ├─ 🥉 第三优先级:内置默认表(已注释,不建议使用) │ ├─ 位置:utils/workflow-util.ts(注释部分) │ ├─ 结构:simpleDefaults[name] = config │ └─ 状态:已注释,保留作为参考 │ └─ 🔄 兜底机制:空对象 {} ├─ 触发:所有优先级都未提供配置时 ├─ 结果:nodeConfig = {} └─ 影响:仍可正常渲染和保存,只是没有默认值 ``` ## 🎨 图标与样式映射树 ```text 视觉映射系统 │ ├─ 🎯 图标映射(getIconByComponentName) │ ├─ answer → 'carbon:question-answering' # 问答图标 │ ├─ classifier → 'carbon:type-pattern' # 分类图标 │ ├─ start → 'carbon:play-outline' # 开始图标 │ ├─ end → 'carbon:closed-caption' # 结束图标 │ ├─ httprequest → 'carbon:http' # HTTP请求图标 │ ├─ dalle3 → 'solar:pallete-2-linear' # 图像生成图标 │ └─ ...(其他节点图标) │ ├─ 🎨 颜色映射(getIconClassByComponentName) │ ├─ answer → 'text-green-800' # 绿色 │ ├─ classifier → 'text-violet-900' # 紫色 │ ├─ start → 'text-blue-900' # 蓝色 │ ├─ end → 'text-orange-800' # 橙色 │ ├─ httprequest → 'text-slate-800' # 灰色 │ ├─ dalle3 → 'text-fuchsia-700' # 紫红色 │ └─ ...(其他节点颜色) │ └─ 🔧 扩展方式 ├─ 新增节点时在工具函数中添加映射 ├─ 图标使用Iconify格式:'collection:icon-name' ├─ 颜色使用Tailwind类名:'text-color-shade' └─ 未配置时显示默认样式 ``` ## 🚀 对外集成API树 ```text 宿主应用集成接口 │ ├─ 📥 Props(输入) │ ├─ workflow: WorkflowInfo # 工作流数据 │ │ ├─ 来源:后端API或新建 │ │ ├─ 内容:nodes、edges、deleteNodes、deleteEdges │ │ └─ 更新:通过emit save事件持久化 │ │ │ ├─ wfComponents: WorkflowComponent[] # 可用组件清单 │ │ ├─ 用途:决定左侧面板显示内容 │ │ ├─ 结构:{ name: string, title: string, remark?: string } │ │ └─ 影响:自动注册节点类型 │ │ │ ├─ componentIdMap: Record # 组件ID映射 │ │ ├─ 用途:后端只返回workflowComponentId时的兼容 │ │ ├─ 结构:{ 1: 'Start', 2: 'Answer', ... } │ │ └─ 回退:未映射时显示'Unknown' │ │ │ └─ saving?: boolean # 保存状态 │ ├─ 用途:控制保存按钮loading/禁用状态 │ ├─ 默认:false │ └─ 建议:宿主在保存过程中设为true │ ├─ 📤 Emits(输出) │ ├─ save(workflow: WorkflowInfo) # 保存事件 │ │ ├─ 触发:用户点击保存按钮 │ │ ├─ 数据:完整的workflow对象(含坐标同步) │ │ └─ 职责:宿主负责持久化到后端 │ │ │ ├─ run(payload: { workflow: WorkflowInfo }) # 运行事件 │ │ ├─ 触发:用户点击运行按钮 │ │ ├─ 数据:包含workflow的运行参数 │ │ └─ 职责:宿主负责触发工作流执行 │ │ │ └─ deleteNode(nodeUuid: string) # 节点删除事件 │ ├─ 触发:节点被删除时 │ ├─ 数据:被删除节点的UUID │ └─ 用途:宿主可监听进行额外处理 │ └─ 🗄️ Store(可选) ├─ setWorkflowDesignerNavigator(fn) # 路由注入 │ ├─ 用途:避免直接耦合宿主路由 │ ├─ 参数:路由跳转函数 │ └─ 使用:store内部调用进行页面跳转 │ └─ useWfStore() # Pinia Store ├─ 状态:工作流列表、运行实例、组件清单 ├─ 方法:CRUD操作、运行态维护 └─ 注意:主组件不强制依赖,宿主可选使用 ``` ## 🔄 关键交互时序树 ```text 用户操作流程 │ ├─ 🚀 初始化流程 │ ├─ 1. 宿主传入workflow和wfComponents │ ├─ 2. 组件onMounted触发 │ ├─ 3. 自动扫描nodes/edges组件并注册 │ ├─ 4. 调用renderGraph()转换数据 │ ├─ 5. 校验节点/边有效性 │ ├─ 6. 写入uiWorkflow并渲染画布 │ └─ 7. 调用fitView()适应画布 │ ├─ 🎯 拖拽新增节点流程 │ ├─ 1. 用户从左侧面板拖拽组件 │ ├─ 2. onPaletteDragStart设置拖拽数据 │ ├─ 3. 拖拽到画布触发onDrop │ ├─ 4. 计算画布坐标位置 │ ├─ 5. 调用createNewNode创建节点 │ │ ├─ 生成UUID和默认配置 │ │ ├─ 推入workflow.nodes │ │ └─ 推入uiWorkflow.nodes │ ├─ 6. 自动选中新节点 │ └─ 7. 显示RightPanel属性面板 │ ├─ 🔗 连线操作流程 │ ├─ 1. 用户拖拽节点连接点 │ ├─ 2. Vue Flow触发onConnect事件 │ ├─ 3. 调用createNewEdge创建边 │ │ ├─ 生成边UUID │ │ ├─ 推入workflow.edges │ │ └─ 推入uiWorkflow.edges │ └─ 4. 画布显示连接线 │ ├─ ⚙️ 属性编辑流程 │ ├─ 1. 用户点击节点选中 │ ├─ 2. onNodeClick触发 │ ├─ 3. 设置selectedWfNode │ ├─ 4. 显示RightPanel │ ├─ 5. 自动匹配专属属性组件 │ │ ├─ 查找properties/NodeProperty.vue │ │ └─ 未找到则使用GenericNodeProperty │ ├─ 6. 用户编辑nodeConfig │ └─ 7. 实时更新workflow.nodes中的数据 │ ├─ 💾 保存流程 │ ├─ 1. 用户点击保存按钮 │ ├─ 2. onSave触发 │ ├─ 3. 同步画布坐标到workflow.nodes │ ├─ 4. 去重deleteEdges数组 │ ├─ 5. emit('save', workflow) │ └─ 6. 宿主接收并持久化到后端 │ └─ 🗑️ 删除操作流程 ├─ 删除节点: │ ├─ 1. 调用onDeleteNode │ ├─ 2. 从workflow.nodes中移除 │ ├─ 3. 从uiWorkflow.nodes中移除 │ ├─ 4. 级联删除相关边 │ ├─ 5. 记录到deleteEdges │ └─ 6. emit('deleteNode', nodeUuid) │ └─ 删除边: ├─ 1. 用户点击边 ├─ 2. onEdgeClick触发 ├─ 3. 从workflow.edges中移除 ├─ 4. 从uiWorkflow.edges中移除 └─ 5. 记录到deleteEdges ``` ## 🎯 扩展开发指南树 ```text 扩展开发步骤 │ ├─ 🔘 新增节点类型 │ ├─ 1. 在wfComponents中添加组件定义 │ │ └─ { name: 'MyNode', title: '我的节点' } │ │ │ ├─ 2. 可选:创建专属节点组件 │ │ ├─ 文件:components/nodes/MyNode.vue │ │ ├─ 内容:节点外观、Handle连接点 │ │ └─ 回退:未提供则使用NodeShell │ │ │ ├─ 3. 可选:创建专属属性面板 │ │ ├─ 文件:properties/MyNodeProperty.vue │ │ ├─ 内容:编辑wfNode.nodeConfig │ │ └─ 回退:未提供则使用GenericNodeProperty │ │ │ ├─ 4. 可选:提供默认配置 │ │ ├─ 方式A:在属性面板中导出getDefaultNodeConfig │ │ ├─ 方式B:在defaults.ts中添加映射 │ │ └─ 回退:空对象{} │ │ │ └─ 5. 可选:添加图标和颜色映射 │ ├─ 在getIconByComponentName中添加case │ └─ 在getIconClassByComponentName中添加case │ ├─ 🔗 新增边类型 │ ├─ 1. 创建边组件文件 │ │ └─ components/edges/MyEdge.vue │ │ │ ├─ 2. 实现边的外观和交互 │ │ ├─ 样式:颜色、粗细、动画 │ │ └─ 交互:点击、悬停效果 │ │ │ └─ 3. 在创建边时指定type │ └─ createNewEdge({ ..., type: 'myedge' }) │ └─ 📝 类型扩展 ├─ 1. 在types/index.d.ts中扩展类型 │ ├─ 新增WorkflowComponent类型 │ └─ 定义节点nodeConfig的TypeScript接口 │ └─ 2. 提供类型安全的开发体验 ├─ IDE智能提示 ├─ 编译时类型检查 └─ 重构时的类型安全 ``` ## 🏗️ 架构设计原则树 ```text 设计原则与取舍 │ ├─ 🎯 核心原则 │ ├─ 最小耦合:不包含鉴权、路由、接口等全局逻辑 │ ├─ 约定优先:通过文件命名约定自动注册组件 │ ├─ 分层设计:业务层与UI层分离,提高可控性 │ └─ 扩展友好:新增节点/边/面板成本最低 │ ├─ 🔄 数据流设计 │ ├─ 单向数据流:workflow → uiWorkflow → 用户操作 → workflow │ ├─ 状态同步:UI层变化实时写回业务层 │ ├─ 删除标记:通过deleteNodes/deleteEdges标记删除 │ └─ 坐标管理:以画布为准,保存时同步到业务层 │ ├─ 🧩 组件设计 │ ├─ 自动注册:扫描文件系统,按约定注册组件 │ ├─ 回退机制:未提供专属组件时使用通用组件 │ ├─ 组合模式:主组件组合各种子组件 │ └─ 职责单一:每个组件只负责特定功能 │ ├─ 🔧 工具函数设计 │ ├─ 纯函数:工具函数无副作用,易于测试 │ ├─ 默认值链:三级优先级的默认值注入 │ ├─ 类型安全:提供完整的TypeScript类型定义 │ └─ 错误处理:优雅处理异常情况 │ └─ 🎨 用户体验设计 ├─ 即时反馈:操作立即反映在UI上 ├─ 状态保持:页面刷新后状态不丢失 ├─ 错误恢复:异常情况下能正常回退 └─ 性能优化:避免不必要的重渲染 ``` ================================================ FILE: apps/web-antd/src/packages/workflow-designer/docs/图标说明.md ================================================ # 工作流设计器图标说明 ## 📍 图标来源 工作流设计器中的图标主要来自两个地方: ### 1. **组件库和节点图标** 🎨 这些图标用于: - 左侧组件库面板中的组件图标 - 画布中节点头部的图标 **图标配置位置:** `apps/web-antd/src/packages/workflow-designer/utils/workflow-util.ts` #### 图标名称配置函数:`getIconByComponentName(name: string)` ```typescript export function getIconByComponentName(name: string) { switch (name.toLowerCase()) { case 'answer': return 'carbon:question-answering' case 'classifier': return 'carbon:type-pattern' case 'knowledgeretrieval': return 'carbon:connect-target' case 'documentextractor': return 'carbon:ibm-knowledge-catalog-standard' case 'keywordextractor': return 'carbon:api-key' case 'faqextractor': return 'fluent-mdl2:book-answers' case 'switcher': return 'oui:logstash-if' case 'template': return 'carbon:prompt-template' case 'dalle3': return 'solar:pallete-2-linear' case 'tongyiwanx': return 'solar:pallete-2-linear' case 'google': return 'ri:google-line' case 'humanfeedback': return 'covid:transmission-virus-human-transmit-2' case 'mailsend': return 'carbon:mail-all' case 'httprequest': return 'carbon:http' case 'end': return 'carbon:closed-caption' case 'start': return 'carbon:play-outline' default: return '' } } ``` #### 图标颜色配置函数:`getIconClassByComponentName(name: string)` ```typescript export function getIconClassByComponentName(name: string) { switch (name.toLowerCase()) { case 'answer': return 'text-green-800' case 'classifier': return 'text-violet-900' case 'knowledgeretrieval': return 'text-stone-900' case 'documentextractor': return 'text-rose-900' case 'keywordextractor': return 'text-cyan-900' case 'faqextractor': return 'text-teal-600' case 'switcher': return 'text-yellow-900' case 'template': return 'text-sky-800' case 'dalle3': return 'text-fuchsia-700' case 'tongyiwanx': return 'text-fuchsia-700' case 'google': return 'text-emerald-900' case 'humanfeedback': return 'text-zinc-800' case 'mailsend': return 'text-amber-800' case 'httprequest': return 'text-slate-800' case 'end': return 'text-orange-800' case 'start': return 'text-blue-900' default: return '' } } ``` **使用示例:** ```vue ``` ### 2. **UI交互图标** 🎯 这些图标用于界面交互元素,来自 `@vben/icons` 套件: #### 常用图标列表: | 功能 | 图标名称 | 使用位置 | |------|---------|---------| | 删除 | `ri:delete-bin-line` | 节点删除菜单 | | 更多操作 | `ri:more-fill` | 节点头部三点菜单 | | 左箭头 | `ri:arrow-left-s-line` | 收起组件库 | | 右箭头 | `ri:arrow-right-s-line` | 展开组件库 | | 加号 | `Plus` | 新建工作流 | | 编辑 | `UserRoundPen` | 编辑工作流 | | 关闭 | `X` | 删除工作流 | | 锁定 | `LockKeyhole` | 未发布状态 | | 外链 | `ExternalLink` | 已发布状态 | | 左箭头 | `ChevronLeft` | 收起侧边栏 | | 右箭头 | `ChevronRight` | 展开侧边栏 | **使用示例:** ```vue // 方式1:直接使用@vben/icons组件 import { Plus } from '@vben/icons' // 方式2:通过SvgIcon组件 ``` ## 🔧 如何添加新的组件图标 当你添加新的工作流组件时,需要配置相应的图标: ### 步骤1:选择图标 访问 [Iconify](https://icon-sets.iconify.design/) 查找合适的图标: - **Carbon Design System**: `carbon:xxx` - IBM设计系统,风格统一 - **Remix Icon**: `ri:xxx` - 简洁现代 - **Solar Icons**: `solar:xxx` - 设计精美 - **Fluent MDL2**: `fluent-mdl2:xxx` - 微软设计语言 ### 步骤2:添加图标配置 编辑 `utils/workflow-util.ts`,在两个函数中添加配置: ```typescript // 1. 添加图标名称 export function getIconByComponentName(name: string) { switch (name.toLowerCase()) { // ... 其他配置 case 'mynewnode': return 'carbon:my-icon' // 👈 添加这行 default: return '' } } // 2. 添加图标颜色(使用Tailwind CSS颜色类) export function getIconClassByComponentName(name: string) { switch (name.toLowerCase()) { // ... 其他配置 case 'mynewnode': return 'text-blue-600' // 👈 添加这行 default: return '' } } ``` ### 步骤3:可选颜色方案 推荐使用的Tailwind颜色类: - `text-blue-900` - 深蓝色(适合主要功能) - `text-green-800` - 深绿色(适合AI/回答类) - `text-violet-900` - 紫色(适合分类/判断) - `text-rose-900` - 玫瑰红(适合提取/处理) - `text-cyan-900` - 青色(适合数据相关) - `text-amber-800` - 琥珀色(适合通知/邮件) - `text-slate-800` - 灰色(适合通用功能) ## 📦 图标组件 ### SvgIcon 组件 **位置:** `apps/web-antd/src/packages/workflow-designer/components/SvgIcon.vue` **实现:** ```vue ``` ### VbenIcon 底层使用 `@vben/icons` 包中的 `VbenIcon` 组件,支持: - ✅ Iconify 所有图标集 - ✅ 按需加载 - ✅ TypeScript 支持 - ✅ 自动颜色和尺寸继承 ## 🎨 样式定制 图标样式可以通过以下方式定制: ```vue ``` ## 📝 最佳实践 1. **保持风格统一**:优先选择同一图标集(如Carbon Design) 2. **语义化命名**:图标应该直观表达组件功能 3. **颜色区分**:不同类型的组件使用不同颜色 4. **尺寸适配**:组件库用20px,节点头部用24px 5. **性能优化**:图标按需加载,不会影响初始加载速度 ## 🔍 调试技巧 如果图标不显示: 1. 检查图标名称是否正确(区分大小写) 2. 确认图标在 Iconify 中存在 3. 检查 `getIconByComponentName` 函数是否返回正确值 4. 查看浏览器控制台是否有加载错误 ## 📚 参考资源 - [Iconify 图标搜索](https://icon-sets.iconify.design/) - [Carbon Design Icons](https://carbondesignsystem.com/guidelines/icons/library/) - [Remix Icon](https://remixicon.com/) - [Tailwind CSS Colors](https://tailwindcss.com/docs/customizing-colors) - [@vben/icons 文档](https://doc.vben.pro/guide/essentials/icons.html) ================================================ FILE: apps/web-antd/src/packages/workflow-designer/docs/流程编排架构说明.md ================================================ ### Workflow Designer(Standalone)架构说明 本包提供一个与业务最小耦合的可视化流程编排设计器,面向宿主应用以“分包”方式复用。核心目标: - 只负责前端可视化编排(画布、节点、连线、表单属性),不包含鉴权/路由/接口等全局逻辑 - 通过 props/emits 对外暴露最小 API,宿主负责数据持久化与运行触发 - 通过“约定优于配置”的自动扫描注册,降低扩展成本(节点、边、属性面板、默认值) ## 依赖与技术栈 - Vue 3 + ` ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/AnswerNodeProperty.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/ClassifierNodeProperty.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/GenericNodeProperty.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/KeywordExtractorNodeProperty.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/StartNodeProperty.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/SwitcherNodeProperty.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/TestNodeProperty.vue ================================================ ================================================ FILE: apps/web-antd/src/packages/workflow-designer/properties/defaults.ts ================================================ import { v4 as uuidv4 } from 'uuid' import type { WorkflowInfo } from '../types/index.d' export type DefaultGetter = (workflow: WorkflowInfo) => any function createUuid() { return uuidv4().replace(/-/g, '') } // 新建节点后的默认配置表 // 统一默认配置表:键名为节点 name 的小写 export const propertyDefaultGetters: Record = { start: () => ({ prologue: '' }), end: () => ({ result: '' }), answer: () => ({ prompt: '', model_name: '' }), template: () => ({ content: '' }), keywordextractor: () => ({ top_n: 5, model_name: '' }), faqextractor: () => ({ top_n: 5, model_name: '' }), knowledgeretrieval: () => ({ knowledge_base_uuid: '', knowledge_base_name: '', score: 0.6, top_n: 3, is_strict: true, default_response: '' }), dalle3: () => ({ prompt: '', size: '1024x1024', quality: 'standard' }), tongyiwanx: () => ({ model_name: '', prompt: '', size: '1024*1024', seed: -1 }), google: () => ({ query: '', country: 'cn', language: 'zh-cn', top_n: 5 }), humanfeedback: () => ({ tip: '' }), mailsend: () => ({ sender_type: 1, cc_mails: '', to_mails: '', subject: '', content: '', smtp: { host: '', port: 465 }, sender: { name: '', mail: '', password: '' } }), httprequest: () => ({ method: 'GET', url: '', content_type: 'text/plain', headers: [{ name: 'Accept', value: '*/*' }], params: [], text_body: '', json_body: {}, form_data_body: [], form_urlencoded_body: [], body: {}, timeout: 10, retry_times: 0, clear_html: false }), classifier: () => ({ model_name: '', categories: [ { category_uuid: createUuid(), category_name: '', target_node_uuid: '' }, { category_uuid: createUuid(), category_name: '', target_node_uuid: '' }, ], }), // 条件分支:基于 workflow 的 Start 节点生成两条示例规则 switcher: (workflow) => { const startNode = workflow.nodes.find((n) => n.wfComponent?.name === 'Start') const firstParam = startNode?.inputConfig?.user_inputs?.[0]?.name || '' return { default_target_node_uuid: '', cases: [ { uuid: createUuid(), operator: 'and', target_node_uuid: '', conditions: [{ node_uuid: startNode?.uuid || '', node_param_name: firstParam, operator: 'contains', value: '' }] }, { uuid: createUuid(), operator: 'and', target_node_uuid: '', conditions: [{ node_uuid: startNode?.uuid || '', node_param_name: firstParam, operator: 'contains', value: '' }] }, ], } }, } ================================================ FILE: apps/web-antd/src/packages/workflow-designer/store/index.ts ================================================ import { defineStore } from 'pinia' import { emptyWorkflowInfo } from '../utils/workflow-util' // 可由宿主应用注入路由跳转方法,提升包的可移植性 let navigateTo: null | ((name: string, params: any) => Promise | void) = null export function setWorkflowDesignerNavigator(fn: (name: string, params: any) => Promise | void) { navigateTo = fn } export const useWfStore = defineStore('wf-store', { state: (): any => { return { showCreateOrEditView: false, createOrEditWfUuid: '', selectedType: 'mine', activeWorkflowInfo: emptyWorkflowInfo(), activeUuid: 'default', wfComponents: [], wfUuidToUIWorkflow: new Map(), myWorkflows: [], publicWorkflows: [], loadingMyWorkflows: false, loadingPublicWorkflows: false, wfUuidToWfRuntimeLoading: new Map(), // is loading workflow instances wfUuidToWfRuntimes: new Map(), operators: [], submitting: false, } }, getters: { getWfRuntimes(state: any) { return (wfUuid: string) => { const records = state.wfUuidToWfRuntimes.get(wfUuid) if (records) return records return [] } }, getStartOrFirstNode(state: any) { return (wfUuid: string) => { const wf = this.getWorkflowInfo(wfUuid) if (!wf) return undefined const start = wf.nodes.find((item: any) => item.wfComponent.name === 'Start') if (start) return start return wf.nodes[0] } }, getStartNode(state: any) { return (wfUuid: string) => { const wf = this.getWorkflowInfo(wfUuid) if (!wf) return undefined return wf.nodes.find((item: any) => item.wfComponent && item.wfComponent.name === 'Start') } }, getStartNodeByWfId(state: any) { return (wfId: string) => { const wf = this.getWorkflowInfoById(wfId) if (!wf) return undefined return wf.nodes.find((item: any) => item.wfComponent.name === 'Start') } }, getWorkflowInfo(state: any) { return (wfUuid: string) => { const wf = state.myWorkflows.find((item: any) => item.uuid === wfUuid) if (wf) return wf return state.publicWorkflows.find((item: any) => item.uuid === wfUuid) } }, getWorkflowInfoById(state: any) { return (id: string) => { const wf = state.myWorkflows.find((item: any) => item.id === id) if (wf) return wf return state.publicWorkflows.find((item: any) => item.id === id) } }, getWfComponent(state: any) { return (name: string) => { return state.wfComponents.find((item: any) => item.name === name) } }, getOperatorDesc(state: any) { return (name: string) => { return state.operators.find((item: any) => item.name === name)?.desc || '' } }, getWfRuntime(state: any) { return (wfRuntimeUuid: string) => { let wfRuntime = null for (const rts of state.wfUuidToWfRuntimes.values()) { wfRuntime = rts.find((item: any) => item.uuid === wfRuntimeUuid) if (wfRuntime) break } if (!wfRuntime) { console.log(`wfRuntime not found: ${wfRuntimeUuid}`) return null } return wfRuntime } }, getRuntimeNode(state: any) { return (wfRuntimeUuid: string, runtimeNodeUuid: string) => { const wfRuntime = this.getWfRuntime(wfRuntimeUuid) if (!wfRuntime) return null const runtimeNode = (wfRuntime.nodes as any[]).find((item: any) => item.uuid === runtimeNodeUuid) if (!runtimeNode) console.log(`runtimeNode not found: ${runtimeNodeUuid}`) return runtimeNode } }, }, actions: { setShowCreateView(status: boolean, wfUuid: string) { console.log(`setShowCreateView: ${status}, ${wfUuid}`) this.showCreateOrEditView = status this.createOrEditWfUuid = wfUuid }, setOperators(operators: any[]) { this.operators = operators }, setActive(wfUuid: string) { this.activeUuid = wfUuid const selected = this.getWorkflowInfo(wfUuid) if (selected) this.activeWorkflowInfo = selected else console.log(`setActive: ${wfUuid} workflow not found`) }, setActiveAndGo(wfUuid: string, defaultViewType?: string) { this.setActive(wfUuid) this.reloadRoute(wfUuid, defaultViewType) }, setLoadingMyWorkflows(status: boolean) { this.loadingMyWorkflows = status }, setLoadingPublicWorkflows(status: boolean) { this.loadingPublicWorkflows = status }, setLoadingRuntimes(currKbUuid: string, status: boolean) { this.wfUuidToWfRuntimeLoading.set(currKbUuid, status) }, setWorkflowComponents(components: any[]) { this.wfComponents = components }, addWorkflowAndActive(info: any) { this.initWorkflowFields(info) this.myWorkflows.unshift(info) this.setActiveAndGo(info.uuid, 'workflowDefine') }, appendWorkflows(infos: any[], isMine: boolean) { const workflows = isMine ? this.myWorkflows : this.publicWorkflows infos.forEach((workflow: any) => { if (workflows.findIndex((wf: any) => wf.uuid === workflow.uuid) !== -1) return this.initWorkflowFields(workflow) workflows.push(workflow) }) }, updateBaseInfo(uuid: string, info: { title: string; remark: string; isPublic: boolean }) { this.myWorkflows.forEach((item: any) => { if (item.uuid === uuid) Object.assign(item, { title: info.title, remark: info.remark, isPublic: info.isPublic }) }) }, initWorkflowFields(workflow: any) { workflow.nodes.forEach((node: any) => { node.workflowUuid = workflow.uuid node.sourceHandleIds = [] const wfComponent = this.wfComponents.find((component: any) => component.id === node.workflowComponentId) if (wfComponent) node.wfComponent = wfComponent if (!node.inputConfig) node.inputConfig = { user_inputs: [], ref_inputs: [] } }) workflow.edges.forEach((edge: any) => { edge.workflowUuid = workflow.uuid }) workflow.deleteEdges = [] workflow.deleteNodes = [] }, updateNodesAndEdges(uuid: string, info: any) { this.myWorkflows.forEach((item: any) => { if (item.uuid === uuid) { item.nodes.forEach((node: any) => { const nodeInfo = info.nodes.find((n: any) => n.uuid === node.uuid) if (nodeInfo) Object.assign(node, { ...nodeInfo }) }) item.edges.forEach((edge: any) => { const edgeInfo = info.edges.find((e: any) => e.uuid === edge.uuid) if (edgeInfo) Object.assign(edge, { ...edgeInfo }) }) } }) }, updateNodesAndEdgesId(uuid: string, updatedWorkflow: any) { this.myWorkflows.forEach((item: any) => { if (item.uuid === uuid) { item.nodes.forEach((node: any) => { if (!node.id) { const updatedNodeInfo = updatedWorkflow.nodes.find((updatedNode: any) => updatedNode.uuid === node.uuid) if (updatedNodeInfo) node.id = updatedNodeInfo.id } }) item.edges.forEach((edge: any) => { if (!edge.id) { const edgeInfo = updatedWorkflow.edges.find((updatedEdge: any) => updatedEdge.uuid === edge.uuid) if (edgeInfo) edge.id = edgeInfo.id } }) } }) }, setWorkflowPublic(uuid: string, publicOrNot: boolean) { const idx = this.myWorkflows.findIndex((item: { uuid: string }) => item.uuid === uuid) if (idx !== -1) this.myWorkflows[idx].isPublic = publicOrNot if (publicOrNot) this.publicWorkflows.push(this.myWorkflows[idx]) else this.publicWorkflows = this.publicWorkflows.filter((item: { uuid: string }) => item.uuid !== uuid) }, deleteWorkflow(uuid: string) { const idx = this.myWorkflows.findIndex((item: { uuid: string }) => item.uuid === uuid) if (idx !== -1) this.myWorkflows.splice(idx, 1) }, updateWfNodeTitle(wfUuid: string, nodeUuid: string, newNodeTitle: string) { this.getWorkflowInfo(wfUuid)?.nodes.forEach((node: any) => { if (node.uuid === nodeUuid) node.title = newNodeTitle }) }, updateWfNode(wfUuid: string, nodeUuid: string, newNode: any) { this.getWorkflowInfo(wfUuid)?.nodes.forEach((node: any) => { if (node.uuid === nodeUuid) Object.assign(node, { ...newNode }) }) }, addRefInputToNode(wfUuid: string, nodeUuid: string, newInput: any) { this.getWorkflowInfo(wfUuid)?.nodes.forEach((node: any) => { if (node.uuid === nodeUuid) node.inputConfig.ref_inputs.push(newInput) }) }, addUserInputToNode(wfUuid: string, nodeUuid: string, newInput: any) { this.getWorkflowInfo(wfUuid)?.nodes.forEach((node: any) => { if (node.uuid === nodeUuid) node.inputConfig.user_inputs.push(newInput) }) }, deleteRefInput(wfUuid: string, nodeUuid: string, idx: number) { this.getWorkflowInfo(wfUuid)?.nodes.forEach((node: any) => { if (node.uuid === nodeUuid) node.inputConfig.ref_inputs.splice(idx, 1) }) }, deleteUserInput(wfUuid: string, nodeUuid: string, idx: number) { this.getWorkflowInfo(wfUuid)?.nodes.forEach((node: any) => { if (node.uuid === nodeUuid) node.inputConfig.user_inputs.splice(idx, 1) }) }, initWfRuntime(wfRuntime: any) { if (!wfRuntime.input) wfRuntime.input = {} if (!wfRuntime.output) wfRuntime.output = {} wfRuntime.nodes = [] }, setWfRuntimes(wfUuid: string, wfRuntimes: any[]) { wfRuntimes.forEach((wfRuntime) => { this.initWfRuntime(wfRuntime) }) this.wfUuidToWfRuntimes.set(wfUuid, wfRuntimes.reverse()) }, updateWfRuntimePrologue(wfRuntimeUuid: string, prologue: string) { const wfRuntime = this.getWfRuntime(wfRuntimeUuid) if (!wfRuntime) return if (prologue) wfRuntime.prologue = prologue }, setWfRuntimeNodes(wfRuntimeUuid: string, nodes: any[]) { const wfRuntime = this.getWfRuntime(wfRuntimeUuid) if (!wfRuntime) return const wfNodes = this.getWorkflowInfoById(wfRuntime.workflowId)?.nodes as any[] if (!wfNodes) { console.error('setWfRuntimeNodes wfNodes not found') return } nodes.forEach((node) => { if (!node.input) node.input = {} if (!node.output) node.output = {} const wfNode = wfNodes.find((n: any) => n.id === node.nodeId) if (!wfNode) { console.error('setWfRuntimeNodes wfNode not found') } else { node.nodeUuid = wfNode.uuid node.nodeTitle = wfNode.title node.wfComponent = wfNode.wfComponent } node.wfRuntimeUuid = wfRuntime.uuid wfRuntime.nodes.push(node) }) }, unshiftWfRuntimes(wfUuid: string, wfRuntimes: any[]) { wfRuntimes.forEach((wfRuntime) => { this.initWfRuntime(wfRuntime) console.log('appendWfRuntime', wfRuntime) }) const records = this.wfUuidToWfRuntimes.get(wfUuid) if (records) records.unshift(...wfRuntimes.reverse()) else this.wfUuidToWfRuntimes.set(wfUuid, wfRuntimes.reverse()) }, appendWfRuntimes(wfUuid: string, wfRuntimes: any[]) { wfRuntimes.forEach((wfRuntime) => { this.initWfRuntime(wfRuntime) console.log('appendWfRuntime', wfRuntime) }) const records = this.wfUuidToWfRuntimes.get(wfUuid) if (records) records.push(...wfRuntimes.reverse()) else this.wfUuidToWfRuntimes.set(wfUuid, wfRuntimes.reverse()) }, // 增加节点运行时信息 appendRuntimeNode(wfRuntimeUuid: string, runtimeNode: any) { const wfRuntime = this.getWfRuntime(wfRuntimeUuid) if (!wfRuntime) return const wfNode = (this.getWorkflowInfoById(wfRuntime.workflowId)?.nodes as any[])?.find((node: any) => node.id === runtimeNode.nodeId) if (wfNode) { runtimeNode.nodeUuid = wfNode.uuid runtimeNode.nodeTitle = wfNode.title runtimeNode.wfComponent = wfNode.wfComponent } else { console.log(`wfNode not found: ${runtimeNode.nodeId}`) } runtimeNode.wfRuntimeUuid = wfRuntime.uuid if (!runtimeNode.input) runtimeNode.input = {} if (!runtimeNode.output) runtimeNode.output = {} wfRuntime.nodes.push(runtimeNode) }, appendInputToRuntimeNode(wfRuntimeUuid: string, runtimeNodeUuid: string, inputJson: string) { const runtimeNode = this.getRuntimeNode(wfRuntimeUuid, runtimeNodeUuid) if (runtimeNode) { // inputJson: {"name": "input1", "content":{"value": "input1", type: 1}} const obj = JSON.parse(inputJson) runtimeNode.input[obj.name] = obj.content } }, appendOutputToRuntimeNode(wfRuntimeUuid: string, runtimeNodeUuid: string, outputJson: string) { const runtimeNode = this.getRuntimeNode(wfRuntimeUuid, runtimeNodeUuid) if (runtimeNode) { const obj = JSON.parse(outputJson) runtimeNode.output[obj.name] = obj.content } }, appendChunkToRuntimeNode(wfRuntimeUuid: string, runtimeNodeUuid: string, chunk: string) { const runtimeNode = this.getRuntimeNode(wfRuntimeUuid, runtimeNodeUuid) // runtimeNode.output 格式: {output:'default output', 'output_name1': 'output_content1'} if (runtimeNode) runtimeNode.output.output = runtimeNode.output.output + chunk }, deleteWfRuntime(wfUuid: string, wfRuntimeUuid: string) { const wfRuntimes = this.wfUuidToWfRuntimes.get(wfUuid) if (wfRuntimes) { const idx = wfRuntimes.findIndex((inst: { uuid: string }) => inst.uuid === wfRuntimeUuid) if (idx > -1) wfRuntimes.splice(idx, 1) } }, updateSuccess(wfUuid: string, wfRuntimeUuid: string, outputJson: string) { if (!wfRuntimeUuid) { console.log('updateSuccess instUuid is empty') return } const wfRuntimes = this.wfUuidToWfRuntimes.get(wfUuid) if (wfRuntimes) { const inst = wfRuntimes.find((inst: { uuid: string }) => inst.uuid === wfRuntimeUuid) if (inst) { inst.status = 3 try { inst.output = JSON.parse(outputJson) } catch (e) { console.error(e) console.log('outputJson is not json', outputJson) } } } }, updateErrorMsg(wfUuid: string, wfRuntimeUuid: string, errorMsg: string) { if (!wfRuntimeUuid) { console.log('updateSuccess instUuid is empty') return } const wfRuntimes = this.wfUuidToWfRuntimes.get(wfUuid) if (wfRuntimes) { const inst = wfRuntimes.find((inst: { uuid: string }) => inst.uuid === wfRuntimeUuid) if (inst) { inst.status = 4 inst.statusRemark = errorMsg || 'error' } } }, clearWfRuntimes(wfUuid: string) { this.wfUuidToWfRuntimes.set(wfUuid, []) }, deleteNode(wfUuid: string, nodeUuid: string) { // Delete node const wf = this.getWorkflowInfo(wfUuid) if (!wf) { console.log('deleteNode wf not found') return } wf.deleteNodes.push(nodeUuid) const idx = wf.nodes.findIndex((node: { uuid: string }) => node.uuid === nodeUuid) if (idx > -1) wf.nodes.splice(idx, 1) this._deleteEdgesByNodeUuid(wf, nodeUuid) this._deleteUiNode(wfUuid, nodeUuid) }, // 删除节点时,删除与之相关的边 _deleteEdgesByNodeUuid(workflow: any, deletedNodeUuid: string) { const edges = workflow.edges.filter((edge: { sourceNodeUuid: string; targetNodeUuid: string }) => edge.sourceNodeUuid === deletedNodeUuid || edge.targetNodeUuid === deletedNodeUuid) edges.forEach((edge: { uuid: string }) => { const edgeIdx = workflow.edges.findIndex( (item: { uuid: string }) => item.uuid === edge.uuid, ) if (edgeIdx > -1) workflow.edges.splice(edgeIdx, 1) workflow.deleteEdges.push(edge.uuid) this._deleteUiEdge(workflow.uuid, edge.uuid) }) }, deleteEdge(wfUuid: string, edgeUuid: string) { // Delete edge const wf = this.getWorkflowInfo(wfUuid) if (!wf) { console.log('deleteEdge wf not found') return } wf.deleteEdges.push(edgeUuid) const idx = wf.edges.findIndex((edge: { uuid: string }) => edge.uuid === edgeUuid) if (idx > -1) wf.edges.splice(idx, 1) this._deleteUiEdge(wfUuid, edgeUuid) }, _deleteUiNode(wfUuid: string, nodeUuid: string) { const uiWorkflow = this.wfUuidToUIWorkflow.get(wfUuid) if (!uiWorkflow) { console.log('_deleteUiNode uiWorkflow not found') return } const idx = uiWorkflow.nodes.findIndex((node: { id: string }) => node.id === nodeUuid) if (idx > -1) uiWorkflow.nodes.splice(idx, 1) }, _deleteUiEdge(wfUuid: string, edgeId: string) { const uiWorkflow = this.wfUuidToUIWorkflow.get(wfUuid) if (!uiWorkflow) { console.log('_deleteUiEdge uiWorkflow not found') return } const idx = uiWorkflow.edges.findIndex((edge: { id: string }) => edge.id === edgeId) if (idx > -1) uiWorkflow.edges.splice(idx, 1) }, async reloadRoute(uuid?: string, defaultViewType?: string) { const viewType = !defaultViewType ? 'instanceList' : defaultViewType if (navigateTo) { await navigateTo('WfDetail', { uuid, viewType }) } else { console.warn('[workflow-designer] 未注入导航器,无法执行路由跳转', { uuid, viewType }) } }, }, }) ================================================ FILE: apps/web-antd/src/packages/workflow-designer/types/index.d.ts ================================================ export interface WorkflowInfo { uuid: string title: string remark?: string userUuid?: string isPublic?: boolean nodes: WorkflowNode[] edges: WorkflowEdge[] deleteNodes?: string[] deleteEdges?: string[] } export interface WorkflowComponent { name: string title: string remark?: string isEnable?: boolean } export interface NodeIODefinition { uuid: string type: number name: string title: string required: boolean multiple?: boolean limit?: number } export interface NodeIOConfig { user_inputs: NodeIODefinition[] ref_inputs: any[] } export interface WorkflowNode { uuid: string title: string workflowUuid: string wfComponent?: WorkflowComponent // 可选,前端可能没有 workflowComponentId?: number // 后端返回的组件ID(新增节点可缺省,保存时补齐) inputConfig: NodeIOConfig nodeConfig: Record outputConfig: Record positionX: number positionY: number } export interface WorkflowEdge { id?: string uuid: string workflowUuid: string sourceNodeUuid: string sourceHandle?: string targetNodeUuid: string } export interface UIWorkflowNodePosition { x: number; y: number } export interface UIWorkflow { nodes: any[] edges: any[] } ================================================ FILE: apps/web-antd/src/packages/workflow-designer/utils/operators.ts ================================================ /** * 条件运算符配置 */ export interface OperatorOption { label: string value: string description?: string } // 条件运算符列表 // 注意: value 值必须与后端 OperatorEnum 完全匹配 export const conditionOperators: OperatorOption[] = [ { label: '包含', value: 'contains', description: '文本包含' }, { label: '不包含', value: 'not contains', description: '文本不包含' }, { label: '开始内容是', value: 'start with', description: '以指定内容开始' }, { label: '结束内容是', value: 'end with', description: '以指定内容结束' }, { label: '为空', value: 'empty', description: '值为空或未定义' }, { label: '不为空', value: 'not empty', description: '值存在且不为空' }, { label: '等于', value: '=', description: '完全相等' }, { label: '不等于', value: '!=', description: '不相等' }, { label: '大于', value: '>', description: '数值大于' }, { label: '大于或等于', value: '>=', description: '数值大于或等于' }, { label: '小于', value: '<', description: '数值小于' }, { label: '小于或等于', value: '<=', description: '数值小于或等于' }, ] // 逻辑运算符列表 export const logicOperators: OperatorOption[] = [ { label: '且(AND)', value: 'and', description: '所有条件都满足' }, { label: '或(OR)', value: 'or', description: '任一条件满足' }, ] /** * 获取运算符显示名称 */ export function getOperatorLabel(value: string): string { const operator = conditionOperators.find(op => op.value === value) return operator?.label || value } /** * 获取逻辑运算符显示名称 */ export function getLogicOperatorLabel(value: string): string { const operator = logicOperators.find(op => op.value === value) return operator?.label || value } ================================================ FILE: apps/web-antd/src/packages/workflow-designer/utils/workflow-util.ts ================================================ import { v4 as uuidv4 } from 'uuid' import type { WorkflowInfo, WorkflowNode, WorkflowEdge, WorkflowComponent, UIWorkflow } from '../types/index.d' import { propertyDefaultGetters } from '../properties/defaults' function deepClone(value: T): T { try { return structuredClone(value) } catch { return JSON.parse(JSON.stringify(value)) } } export function emptyWorkflowInfo(): WorkflowInfo { return { uuid: 'default', title: '', nodes: [], edges: [], deleteNodes: [], deleteEdges: [], } } export function emptyWorkflowNode(): WorkflowNode { return { uuid: createUuid(), title: '', workflowUuid: 'default', wfComponent: { name: 'Start', title: '开始' }, inputConfig: { user_inputs: [], ref_inputs: [] }, nodeConfig: {}, outputConfig: {}, positionX: 0, positionY: 0, } } function createUuid() { return uuidv4().replace(/-/g, '') } // 默认配置:优先读取面板自定义导出,其次读取集中配置表,最后内置表 const propertyModules = import.meta.glob('../properties/*NodeProperty.vue', { eager: true }) as Record function toPropertyKey(path: string) { const file = path.substring(path.lastIndexOf('/') + 1) return file.replace(/NodeProperty\.vue$/, '').toLowerCase() } const propertySelfGetters: Record any> = {} for (const [p, mod] of Object.entries(propertyModules)) { const key = toPropertyKey(p) const getter = (mod as any)?.getDefaultNodeConfig as ((wf: WorkflowInfo) => any) | undefined if (typeof getter === 'function') propertySelfGetters[key] = getter } function getDefaultNodeConfig(name: string, workflow: WorkflowInfo) { const key = (name || '').toLowerCase() // 1) 面板自身导出 const selfGetter = propertySelfGetters[key] if (selfGetter) return selfGetter(workflow) // 2) 集中配置表 const tableGetter = propertyDefaultGetters[key] if (tableGetter) return tableGetter(workflow) // 3) 内置简易默认表,可在defaults.ts中添加其他默认配置,不建议使用该内置配置表 // const simpleDefaults: Record = { // start: { prologue: '' }, // end: { result: '' }, // answer: { prompt: '', model_name: '' }, // template: { content: '' }, // keywordextractor: { top_n: 5, model_name: '' }, // faqextractor: { top_n: 5, model_name: '' }, // knowledgeretrieval: { knowledge_base_uuid: '', knowledge_base_name: '', score: 0.6, top_n: 3, is_strict: true, default_response: '' }, // dalle3: { prompt: '', size: '1024x1024', quality: 'standard' }, // tongyiwanx: { model_name: '', prompt: '', size: '1024*1024', seed: -1 }, // google: { query: '', country: 'cn', language: 'zh-cn', top_n: 5 }, // humanfeedback: { tip: '' }, // mailsend: { sender_type: 1, cc_mails: '', to_mails: '', subject: '', content: '', smtp: { host: '', port: 465 }, sender: { name: '', mail: '', password: '' } }, // httprequest: { method: 'GET', url: '', content_type: 'text/plain', headers: [{ name: 'Accept', value: '*/*' }], params: [], text_body: '', json_body: {}, form_data_body: [], form_urlencoded_body: [], body: {}, timeout: 10, retry_times: 0, clear_html: false }, // classifier: { categories: [ { category_uuid: createUuid(), category_name: '', target_node_uuid: '' }, { category_uuid: createUuid(), category_name: '', target_node_uuid: '' } ] }, // } // return simpleDefaults[key] ?? {} } export function createNewNode( workflow: WorkflowInfo, uiWorkflow: UIWorkflow, component: WorkflowComponent, position: { x: number; y: number }, ) { const newWfNode = emptyWorkflowNode() newWfNode.uuid = createUuid() newWfNode.title = component.title newWfNode.workflowUuid = workflow.uuid newWfNode.wfComponent = component newWfNode.inputConfig = { user_inputs: [], ref_inputs: [] } // 使用映射初始化最小可用的 nodeConfig,特殊项(如 switcher)按需读取 workflow newWfNode.nodeConfig = deepClone(getDefaultNodeConfig(component.name, workflow) || {}) newWfNode.outputConfig = {} newWfNode.positionX = position.x newWfNode.positionY = position.y workflow.nodes.push(newWfNode) uiWorkflow.nodes.push(wfNodeToUiNode(newWfNode)) } export function createNewEdge(params: { workflow: WorkflowInfo uiWorkflow: UIWorkflow source: string sourceHandle: string target: string }) { const wfEdge: WorkflowEdge = { id: '', uuid: uuidv4().replace(/-/g, ''), workflowUuid: params.workflow.uuid, sourceNodeUuid: params.source, sourceHandle: params.sourceHandle, targetNodeUuid: params.target, } params.workflow.edges.push(wfEdge) if (params.target) { const uiEdge = { id: wfEdge.uuid, source: wfEdge.sourceNodeUuid, target: wfEdge.targetNodeUuid, type: 'special', animated: true, sourceHandle: params.sourceHandle ? params.sourceHandle : undefined, data: wfEdge, } params.uiWorkflow.edges.push(uiEdge) } } export function updateEdgeBySourceHandle(params: { workflow: WorkflowInfo uiWorkflow: UIWorkflow source: string sourceHandle: string target: string }) { const wfEdge = params.workflow.edges.find((item) => item.sourceHandle === params.sourceHandle) if (!wfEdge) return wfEdge.targetNodeUuid = params.target const idx = (params.uiWorkflow.edges as any[]).findIndex((item: any) => item.source === params.source && item.sourceHandle === params.sourceHandle) if (idx > -1) (params.uiWorkflow.edges as any[]).splice(idx, 1) const uiEdge = { id: wfEdge.uuid, source: wfEdge.sourceNodeUuid, target: wfEdge.targetNodeUuid, animated: true, sourceHandle: params.sourceHandle, } ;(params.uiWorkflow.edges as any[]).push(uiEdge) } export function deleteEdgesBySourceHandle( workflow: WorkflowInfo, uiWorkflow: UIWorkflow, source: string, sourceHandle: string, ) { const edgeIndex = workflow.edges.findIndex((edge) => { const hit = edge.sourceNodeUuid === source && edge.sourceHandle === sourceHandle if (hit && !workflow.deleteEdges) workflow.deleteEdges = [] if (hit) workflow.deleteEdges!.push(edge.uuid) return hit }) if (edgeIndex !== -1) workflow.edges.splice(edgeIndex, 1) const uiEdgeIndex = (uiWorkflow.edges as any[]).findIndex((edge: any) => edge.sourceNodeUuid === source && edge.sourceHandle === sourceHandle) if (uiEdgeIndex !== -1) (uiWorkflow.edges as any[]).splice(uiEdgeIndex, 1) } function wfNodeToUiNode(node: WorkflowNode) { return { id: node.uuid, type: node.wfComponent?.name.toLowerCase() ?? '', data: node, position: { x: node.positionX, y: node.positionY }, } } export function getNameByInputType(type: number) { switch (type) { case 1: return '文本' case 2: return '数字' case 3: return '下拉选项' case 4: return '文件列表' default: return 'Unknown' } } export function getIconByComponentName(name: string) { switch (name.toLowerCase()) { case 'answer': return 'carbon:question-answering' case 'classifier': return 'carbon:type-pattern' case 'knowledgeretrieval': return 'carbon:connect-target' case 'documentextractor': return 'carbon:ibm-knowledge-catalog-standard' case 'keywordextractor': return 'carbon:api-key' case 'faqextractor': return 'fluent-mdl2:book-answers' case 'switcher': return 'oui:logstash-if' case 'template': return 'carbon:prompt-template' case 'dalle3': return 'solar:pallete-2-linear' case 'tongyiwanx': return 'solar:pallete-2-linear' case 'google': return 'ri:google-line' case 'humanfeedback': return 'covid:transmission-virus-human-transmit-2' case 'mailsend': return 'carbon:mail-all' case 'httprequest': return 'carbon:http' case 'end': return 'carbon:closed-caption' case 'start': return 'carbon:play-outline' default: return '' } } export function getIconClassByComponentName(name: string) { switch (name.toLowerCase()) { case 'answer': return 'text-green-800' case 'classifier': return 'text-violet-900' case 'knowledgeretrieval': return 'text-stone-900' case 'documentextractor': return 'text-rose-900' case 'keywordextractor': return 'text-cyan-900' case 'faqextractor': return 'text-teal-600' case 'switcher': return 'text-yellow-900' case 'template': return 'text-sky-800' case 'dalle3': return 'text-fuchsia-700' case 'tongyiwanx': return 'text-fuchsia-700' case 'google': return 'text-emerald-900' case 'humanfeedback': return 'text-zinc-800' case 'mailsend': return 'text-amber-800' case 'httprequest': return 'text-slate-800' case 'end': return 'text-orange-800' case 'start': return 'text-blue-900' default: return '' } } ================================================ FILE: apps/web-antd/src/preferences.ts ================================================ import { defineOverridesPreferences } from '@vben/preferences'; /** * @description 项目配置文件 * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 * !!! 更改配置后请清空缓存,否则可能不生效 */ export const overridesPreferences = defineOverridesPreferences({ // overrides app: { /** * 不要动这里 后端路由模式 */ accessMode: 'backend', /** * 不需要refresh token 由后端处理 */ enableRefreshToken: false, /** * 这里可以设置默认头像 url链接或vite导入的图片链接 */ // defaultAvatar: '', /** * 在这里设置应用标题 */ name: import.meta.env.VITE_APP_TITLE, /** * 不支持modal模式 需要改动的地方太多 * 1. 正常重新登录后不会再触发接口请求 即触发登录超时的页面为空数据 * 2. 切换租户登录后不会重新加载菜单 */ // loginExpiredMode: 'modal', }, footer: { /** * 不显示footer */ enable: false, }, tabbar: { /** * 标签tab 持久化 关闭 */ persist: false, // styleType: 'card', }, theme: { /** * 浅色sidebar */ semiDarkSidebar: false, /** * 圆角大小 换算比例为1.6px = 0.1radius * 这里为6px 与antd保持一致 */ radius: '0.375', }, /** * !!! 更改配置后请清空浏览器缓存 * 在这里更换logo * source可选值: * 1. 本地public目录下的图片 需要加上/ 比如:/logo.png * 2. 网络图片链接 * 3. vite导入的图片 import xxx from 'xxx.png' * * !!! 更改配置后请清空浏览器缓存 */ // logo: { // enable: true, // source: '', // }, }); ================================================ FILE: apps/web-antd/src/router/access.ts ================================================ import type { ComponentRecordType, GenerateMenuAndRoutesOptions, RouteMeta, RouteRecordStringComponent, } from '@vben/types'; import type { Menu } from '#/api'; import { generateAccessible } from '@vben/access'; import { preferences } from '@vben/preferences'; import { message } from 'ant-design-vue'; import { cloneDeep } from 'lodash-es'; import { getAllMenusApi } from '#/api'; import { BasicLayout, IFrameView } from '#/layouts'; import { $t } from '#/locales'; import { localMenuList } from './routes/local'; const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); const NotFoundComponent = () => import('#/views/_core/fallback/not-found.vue'); /** * 后端返回的meta有时候不包括需要的信息 比如activePath等 * 在这里定义映射 */ const routeMetaMapping: Record> = { '/system/role-auth/user/:roleId': { activePath: '/system/role', requireHomeRedirect: true, }, '/system/oss-config/index': { activePath: '/system/oss', requireHomeRedirect: true, }, '/tool/gen-edit/index/:tableId': { activePath: '/tool/gen', requireHomeRedirect: true, }, '/workflow/design/index': { activePath: '/workflow/processDefinition', requireHomeRedirect: true, }, '/workflow/leaveEdit/index': { activePath: '/demo/leave', requireHomeRedirect: true, }, }; /** * 后台路由转vben路由 * @param menuList 后台菜单 * @param parentPath 上级目录 * @returns vben路由 */ function backMenuToVbenMenu( menuList: Menu[], parentPath = '', ): RouteRecordStringComponent[] { const resultList: RouteRecordStringComponent[] = []; menuList.forEach((menu) => { // 根目录为菜单形式 // 固定有一个children children为当前菜单 if (menu.path === '/' && menu.children && menu.children.length === 1) { if (!menu.children || !menu.children[0]) { return; } // 需要处理根目录为内嵌的情况 不会带InnerLink if (/^https?:\/\//.test(menu.children[0].path)) { menu.children[0].component = 'InnerLink'; menu.children[0].path = menu.children[0].path .replaceAll(/^https?:\/\//g, '') .replaceAll('/#/', '') .replaceAll('#', '') .replaceAll(/[?&]/g, ''); } // 取子路径作为父级路径 const path = menu.children[0].path; // 取子菜单的meta作为当前菜单的meta menu.meta = menu.children[0].meta; // 由于在一级路由 父级路径需要加上/ menu.path = `/${path}`; menu.component = 'RootMenu'; // 将子路径设置为'' menu.children[0].path = ''; } // 外链: http开头 & 组件为Layout || ParentView // 正则判断是否为http://或者https://开头 if ( /^https?:\/\//.test(menu.path) && (menu.component === 'Layout' || menu.component === 'ParentView') ) { menu.component = 'Link'; } // 内嵌iframe 组件为InnerLink if (menu.meta?.link && menu.component === 'InnerLink') { menu.component = 'IFrameView'; } /** * 拼接path * menu.path为''(根目录路由) 则不拼接 */ if (parentPath && menu.path) { menu.path = `${parentPath}/${menu.path}`; } // 创建vben路由对象 const vbenRoute: RouteRecordStringComponent = { component: menu.component, meta: { // 当前路由不在菜单显示 但是可以通过链接访问 // 不可访问的路由由后端控制隐藏(不返回对应路由) hideInMenu: menu.hidden, icon: menu.meta?.icon, keepAlive: !menu.meta?.noCache, title: menu.meta?.title, }, name: menu.name, path: menu.path, }; // 处理meta映射 if (Object.keys(routeMetaMapping).includes(vbenRoute.path)) { const routeMeta = routeMetaMapping[vbenRoute.path]; if (routeMeta) { vbenRoute.meta = { ...vbenRoute.meta, ...(routeMeta as RouteMeta), }; } } // 添加路由参数信息 if (menu.query) { try { const query = JSON.parse(menu.query); vbenRoute.meta && (vbenRoute.meta.query = query); } catch { console.error('错误的路由参数类型, 必须为[json]格式'); } } /** * 处理不同组件 */ switch (menu.component) { /** * iframe内嵌 */ case 'IFrameView': { vbenRoute.component = 'IFrameView'; if (vbenRoute.meta) { vbenRoute.meta.iframeSrc = menu.meta.link; } /** * 需要判断特殊情况 比如vue的hash是带#的 * 比如链接 aaa.com/#/bbb path会转换为 aaa/com/#/bbb * 比如链接 aaa.com/?bbb=xxx * 需要去除# 否则无法被添加到路由 */ vbenRoute.path = vbenRoute.path // 替换https:// 或者 http:// .replaceAll(/^https?:\/\//g, '') .replaceAll('/#/', '') .replaceAll('#', '') .replaceAll(/[?&]/g, ''); break; } case 'Layout': { vbenRoute.component = 'BasicLayout'; break; } /** * 外链 新窗口打开 */ case 'Link': { if (vbenRoute.meta) { vbenRoute.meta.link = menu.meta.link; } vbenRoute.component = 'BasicLayout'; break; } /** * 三级以上菜单 父级component为ParentView * 不能为layout 会套两层BasicLayout */ case 'ParentView': { vbenRoute.component = ''; break; } /** * 根目录菜单 */ case 'RootMenu': { if (vbenRoute.meta) { vbenRoute.meta.hideChildrenInMenu = true; } vbenRoute.component = 'BasicLayout'; break; } /** * 其他自定义组件 如system/user/index 拼接/ */ default: { vbenRoute.component = `/${menu.component}`; break; } } // children处理 if (menu.children && menu.children.length > 0) { vbenRoute.children = backMenuToVbenMenu(menu.children, menu.path); } // 添加 resultList.push(vbenRoute); }); return resultList; } async function generateAccess(options: GenerateMenuAndRoutesOptions) { const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); const layoutMap: ComponentRecordType = { BasicLayout, IFrameView, NotFoundComponent, }; return await generateAccessible(preferences.app.accessMode, { ...options, fetchMenuListAsync: async () => { // 清除以前的message message.destroy(); message.loading({ content: `${$t('common.loadingMenu')}...`, duration: 1, }); // 后台返回路由/菜单 const backMenuList = await getAllMenusApi(); // 转换为vben能用的路由 const vbenMenuList = backMenuToVbenMenu(backMenuList); // 特别注意 这里要深拷贝 const menuList = [...cloneDeep(localMenuList), ...vbenMenuList]; // console.log('menuList', menuList); return menuList; }, // 可以指定没有权限跳转403页面 forbiddenComponent, // 如果 route.meta.menuVisibleWithForbidden = true layoutMap, pageMap, }); } export { generateAccess }; ================================================ FILE: apps/web-antd/src/router/guard.ts ================================================ import type { Router } from 'vue-router'; import { LOGIN_PATH } from '@vben/constants'; import { preferences } from '@vben/preferences'; import { useAccessStore, useUserStore } from '@vben/stores'; import { startProgress, stopProgress } from '@vben/utils'; import { accessRoutes, coreRouteNames } from '#/router/routes'; import { useAuthStore } from '#/store'; import { generateAccess } from './access'; /** * 通用守卫配置 * @param router */ function setupCommonGuard(router: Router) { // 记录已经加载的页面 const loadedPaths = new Set(); router.beforeEach((to) => { to.meta.loaded = loadedPaths.has(to.path); // 页面加载进度条 if (!to.meta.loaded && preferences.transition.progress) { startProgress(); } return true; }); router.afterEach((to) => { // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 loadedPaths.add(to.path); // 关闭页面加载进度条 if (preferences.transition.progress) { stopProgress(); } }); } /** * 权限访问守卫配置 * @param router */ function setupAccessGuard(router: Router) { router.beforeEach(async (to, from) => { const accessStore = useAccessStore(); const userStore = useUserStore(); const authStore = useAuthStore(); // 基本路由,这些路由不需要进入权限拦截 if (coreRouteNames.includes(to.name as string)) { if (to.path === LOGIN_PATH && accessStore.accessToken) { return decodeURIComponent( (to.query?.redirect as string) || userStore.userInfo?.homePath || preferences.app.defaultHomePath, ); } return true; } // accessToken 检查 if (!accessStore.accessToken) { // 明确声明忽略权限访问权限,则可以访问 if (to.meta.ignoreAccess) { return true; } // 没有访问权限,跳转登录页面 if (to.fullPath !== LOGIN_PATH) { return { path: LOGIN_PATH, // 如不需要,直接删除 query query: to.fullPath === preferences.app.defaultHomePath ? {} : { redirect: encodeURIComponent(to.fullPath) }, // 携带当前跳转的页面,登录后重新跳转该页面 replace: true, }; } return to; } // 是否已经生成过动态路由 if (accessStore.isAccessChecked) { return true; } // 生成路由表 // 当前登录用户拥有的角色标识列表 const userInfo = userStore.userInfo || (await authStore.fetchUserInfo()); const userRoles = userInfo.roles ?? []; // 生成菜单和路由 const { accessibleMenus, accessibleRoutes } = await generateAccess({ roles: userRoles, router, // 则会在菜单中显示,但是访问会被重定向到403 routes: accessRoutes, }); // 保存菜单信息和路由信息 accessStore.setAccessMenus(accessibleMenus); accessStore.setAccessRoutes(accessibleRoutes); accessStore.setIsAccessChecked(true); const redirectPath = (from.query.redirect ?? (to.path === preferences.app.defaultHomePath ? userInfo.homePath || preferences.app.defaultHomePath : to.fullPath)) as string; return { ...router.resolve(decodeURIComponent(redirectPath)), replace: true, }; }); } /** * 项目守卫配置 * @param router */ function createRouterGuard(router: Router) { /** 通用 */ setupCommonGuard(router); /** 权限访问 */ setupAccessGuard(router); } export { createRouterGuard }; ================================================ FILE: apps/web-antd/src/router/index.ts ================================================ import { createRouter, createWebHashHistory, createWebHistory, } from 'vue-router'; import { resetStaticRoutes } from '@vben/utils'; import { createRouterGuard } from './guard'; import { routes } from './routes'; /** * @zh_CN 创建vue-router实例 */ const router = createRouter({ history: import.meta.env.VITE_ROUTER_HISTORY === 'hash' ? createWebHashHistory(import.meta.env.VITE_BASE) : createWebHistory(import.meta.env.VITE_BASE), // 应该添加到路由的初始路由列表。 routes, scrollBehavior: (to, _from, savedPosition) => { if (savedPosition) { return savedPosition; } return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; }, // 是否应该禁止尾部斜杠。 // strict: true, }); const resetRoutes = () => resetStaticRoutes(router, routes); // 创建路由守卫 createRouterGuard(router); export { resetRoutes, router }; ================================================ FILE: apps/web-antd/src/router/routes/core.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import { LOGIN_PATH } from '@vben/constants'; import { preferences } from '@vben/preferences'; import { $t } from '#/locales'; const BasicLayout = () => import('#/layouts/basic.vue'); const AuthPageLayout = () => import('#/layouts/auth.vue'); /** 全局404页面 */ const fallbackNotFoundRoute: RouteRecordRaw = { component: () => import('#/views/_core/fallback/not-found.vue'), meta: { hideInBreadcrumb: true, hideInMenu: true, hideInTab: true, title: '404', }, name: 'FallbackNotFound', path: '/:path(.*)*', }; /** 基本路由,这些路由是必须存在的 */ const coreRoutes: RouteRecordRaw[] = [ /** * 根路由 * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 * 此路由必须存在,且不应修改 */ { component: BasicLayout, meta: { hideInBreadcrumb: true, title: 'Root', }, name: 'Root', path: '/', redirect: preferences.app.defaultHomePath, children: [], }, { component: () => import('#/views/_core/social-callback/index.vue'), meta: { title: $t('page.auth.oauthLogin'), }, name: 'OAuthRedirect', path: '/social-callback', }, { component: AuthPageLayout, meta: { hideInTab: true, title: 'Authentication', }, name: 'Authentication', path: '/auth', redirect: LOGIN_PATH, children: [ { name: 'Login', path: 'login', component: () => import('#/views/_core/authentication/login.vue'), meta: { title: $t('page.auth.login'), }, }, { name: 'CodeLogin', path: 'code-login', component: () => import('#/views/_core/authentication/code-login.vue'), meta: { title: $t('page.auth.codeLogin'), }, }, { name: 'QrCodeLogin', path: 'qrcode-login', component: () => import('#/views/_core/authentication/qrcode-login.vue'), meta: { title: $t('page.auth.qrcodeLogin'), }, }, { name: 'ForgetPassword', path: 'forget-password', component: () => import('#/views/_core/authentication/forget-password.vue'), meta: { title: $t('page.auth.forgetPassword'), }, }, { name: 'Register', path: 'register', component: () => import('#/views/_core/authentication/register.vue'), meta: { title: $t('page.auth.register'), }, }, ], }, ]; export { coreRoutes, fallbackNotFoundRoute }; ================================================ FILE: apps/web-antd/src/router/routes/index.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; import { coreRoutes, fallbackNotFoundRoute } from './core'; const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { eager: true, }); // 有需要可以自行打开注释,并创建文件夹 // const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); // const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); /** 动态路由 */ const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); /** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ // const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); // const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); const staticRoutes: RouteRecordRaw[] = []; const externalRoutes: RouteRecordRaw[] = []; /** 路由列表,由基本路由、外部路由和404兜底路由组成 * 无需走权限验证(会一直显示在菜单中) */ const routes: RouteRecordRaw[] = [ ...coreRoutes, ...dynamicRoutes, ...externalRoutes, fallbackNotFoundRoute, ]; /** 基本路由(登录, 第三方登录, 注册等) */ const basicRoutes = [...coreRoutes]; /** 基本路由列表,这些路由不需要进入权限拦截 */ const coreRouteNames = traverseTreeValues(basicRoutes, (route) => route.name); /** 有权限校验的路由列表,包含动态路由和静态路由 */ const accessRoutes = [...dynamicRoutes, ...staticRoutes]; export { accessRoutes, coreRouteNames, routes }; ================================================ FILE: apps/web-antd/src/router/routes/local.ts ================================================ import type { RouteRecordStringComponent } from '@vben/types'; import { $t } from '@vben/locales'; const { version, // vite inject-metadata 插件注入的全局变量 } = __VBEN_ADMIN_METADATA__ || {}; /** * 该文件放非后台返回的路由 比如个人中心 等需要跳转显示的页面 * 也可以直接在菜单管理配置 */ const localRoutes: RouteRecordStringComponent[] = [ { component: '/_core/profile/index', meta: { icon: 'mingcute:profile-line', title: $t('ui.widgets.profile'), hideInMenu: true, requireHomeRedirect: true, }, name: 'Profile', path: '/profile', }, ]; /** * 这里放本地路由 */ export const localMenuList: RouteRecordStringComponent[] = [ { component: 'BasicLayout', meta: { order: -1, title: 'page.dashboard.title', // 不使用基础布局(仅在顶级生效) noBasicLayout: true, }, name: 'Dashboard', path: '/', redirect: '/analytics', children: [ { name: 'Analytics', path: '/analytics', component: '/dashboard/analytics/index', meta: { icon: 'lucide:book-open-text', affixTab: true, title: 'page.dashboard.analytics', }, }, { name: 'Workspace', path: '/workspace', component: '/dashboard/workspace/index', meta: { icon: 'icon-park-outline:workbench', title: 'page.dashboard.workspace', }, }, ], }, // { // component: '/_core/about/index', // meta: { // icon: 'lucide:copyright', // order: 9999, // title: $t('demos.vben.about'), // }, // name: 'About', // path: '/vben-admin/about', // }, ...localRoutes, ]; ================================================ FILE: apps/web-antd/src/router/routes/modules/aiflow.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; const routes: RouteRecordRaw[] = [ { meta: { hideInMenu: true, }, name: 'WorkflowEdit', path: '/aiflow/edit/:uuid', component: () => import('#/views/aiflow/edit.vue'), }, { meta: { hideInMenu: true, }, name: 'WorkflowRun', path: '/aiflow/run/:uuid', component: () => import('#/views/aiflow/run.vue'), }, ]; export default routes; ================================================ FILE: apps/web-antd/src/router/routes/modules/dashboard.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import { $t } from '#/locales'; const routes: RouteRecordRaw[] = [ { meta: { icon: 'lucide:layout-dashboard', order: -1, title: $t('page.dashboard.title'), }, name: 'Dashboard', path: '/dashboard', children: [ { name: 'Analytics', path: '/analytics', component: () => import('#/views/dashboard/analytics/index.vue'), meta: { affixTab: true, icon: 'lucide:area-chart', title: $t('page.dashboard.analytics'), }, }, { name: 'Workspace', path: '/workspace', component: () => import('#/views/dashboard/workspace/index.vue'), meta: { icon: 'carbon:workspace', title: $t('page.dashboard.workspace'), }, }, ], }, ]; export default routes; ================================================ FILE: apps/web-antd/src/router/routes/modules/knowledge.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; const BasicLayout = () => import('#/layouts/basic.vue'); const routes: RouteRecordRaw[] = [ { component: BasicLayout, meta: { hideInBreadcrumb: true, title: 'Knowledge Base', hideInMenu: true, }, name: 'KnowledgeBaseRoot', path: '/knowledge', children: [ { name: 'KnowledgeInfoDetail', path: 'info/detail/:id', component: () => import('#/views/knowledge/info/detail/index.vue'), meta: { title: '知识库详情', hideInMenu: true, }, }, ], }, ]; export default routes; ================================================ FILE: apps/web-antd/src/router/routes/modules/vben.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import { VBEN_DOC_URL, VBEN_ELE_PREVIEW_URL, VBEN_GITHUB_URL, VBEN_LOGO_URL, VBEN_NAIVE_PREVIEW_URL, } from '@vben/constants'; import { IFrameView } from '#/layouts'; import { $t } from '#/locales'; const routes: RouteRecordRaw[] = [ { meta: { badgeType: 'dot', icon: VBEN_LOGO_URL, order: 9998, title: $t('demos.vben.title'), }, name: 'VbenProject', path: '/vben-admin', children: [ { name: 'VbenAbout', path: '/vben-admin/about', component: () => import('#/views/_core/about/index.vue'), meta: { icon: 'lucide:copyright|offline', title: $t('demos.vben.about'), }, }, { name: 'VbenDocument', path: '/vben-admin/document', component: IFrameView, meta: { icon: 'lucide:book-open-text|offline', link: VBEN_DOC_URL, title: $t('demos.vben.document'), }, }, { name: 'VbenGithub', path: '/vben-admin/github', component: IFrameView, meta: { icon: 'mdi:github', link: VBEN_GITHUB_URL, title: 'Github', }, }, { name: 'VbenNaive', path: '/vben-admin/naive', component: IFrameView, meta: { badgeType: 'dot', icon: 'logos:naiveui', link: VBEN_NAIVE_PREVIEW_URL, title: $t('demos.vben.naive-ui'), }, }, { name: 'VbenElementPlus', path: '/vben-admin/ele', component: IFrameView, meta: { badgeType: 'dot', icon: 'logos:element', link: VBEN_ELE_PREVIEW_URL, title: $t('demos.vben.element-plus'), }, }, ], }, { name: 'VbenAbout', path: '/vben-admin/about', component: () => import('#/views/_core/about/index.vue'), meta: { icon: 'lucide:copyright', title: $t('demos.vben.about'), order: 9999, }, }, ]; export default routes; ================================================ FILE: apps/web-antd/src/store/auth.ts ================================================ import type { LoginAndRegisterParams } from '@vben/common-ui'; import type { UserInfo } from '@vben/types'; import { ref } from 'vue'; import { useRouter } from 'vue-router'; import { LOGIN_PATH } from '@vben/constants'; import { preferences } from '@vben/preferences'; import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; import { notification } from 'ant-design-vue'; import { defineStore } from 'pinia'; import { doLogout, getUserInfoApi, loginApi, seeConnectionClose } from '#/api'; import { ImpossibleReturn401Exception, UnauthorizedException, } from '#/api/helper'; import { $t } from '#/locales'; import { useDictStore } from './dict'; export const useAuthStore = defineStore('auth', () => { const accessStore = useAccessStore(); const userStore = useUserStore(); const router = useRouter(); const loginLoading = ref(false); /** * 异步处理登录操作 * Asynchronously handle the login process * @param params 登录表单数据 */ async function authLogin( params: LoginAndRegisterParams, onSuccess?: () => Promise | void, ) { // 异步处理用户登录操作并获取 accessToken let userInfo: null | UserInfo = null; try { loginLoading.value = true; const { access_token } = await loginApi(params); // 将 accessToken 存储到 accessStore 中 accessStore.setAccessToken(access_token); accessStore.setRefreshToken(access_token); // 获取用户信息并存储到 accessStore 中 userInfo = await fetchUserInfo(); /** * 设置用户信息 */ userStore.setUserInfo(userInfo); /** * 在这里设置权限 */ accessStore.setAccessCodes(userInfo.permissions); if (accessStore.loginExpired) { accessStore.setLoginExpired(false); } else { onSuccess ? await onSuccess?.() : await router.push(preferences.app.defaultHomePath); } if (userInfo?.realName) { notification.success({ description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, duration: 3, message: $t('authentication.loginSuccess'), }); } } finally { loginLoading.value = false; } return { userInfo, }; } async function logout(redirect: boolean = true) { try { // 这两个接口不依赖 不需要await sseClose await Promise.all([seeConnectionClose(), doLogout()]); } catch (error) { console.error(error); /** * 这两个接口按正常逻辑不可能返回401 * 在微服务版本配置错误的情况下 这里会抛出401 * 在这里抛出自定义异常供上层处理 */ if (error instanceof UnauthorizedException) { throw new ImpossibleReturn401Exception(error.message); } } finally { resetAllStores(); accessStore.setLoginExpired(false); // 回登陆页带上当前路由地址 await router.replace({ path: LOGIN_PATH, query: redirect ? { redirect: encodeURIComponent(router.currentRoute.value.fullPath), } : {}, }); } } async function fetchUserInfo() { const backUserInfo = await getUserInfoApi(); /** * 登录超时的情况 */ if (!backUserInfo) { throw new Error('获取用户信息失败.'); } const { permissions = [], roles = [], user } = backUserInfo; /** * 从后台user -> vben user转换 */ const userInfo: UserInfo = { avatar: user.avatar ?? '', permissions, realName: user.nickName, roles, userId: user.userId, username: user.userName, email: user.email ?? '', }; userStore.setUserInfo(userInfo); /** * 需要重新加载字典 * 比如退出登录切换到其他租户 */ const dictStore = useDictStore(); dictStore.resetCache(); return userInfo; } function $reset() { loginLoading.value = false; } return { $reset, authLogin, fetchUserInfo, loginLoading, logout, }; }); ================================================ FILE: apps/web-antd/src/store/dict.ts ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { DictData } from '#/api/system/dict/dict-data-model'; import { reactive } from 'vue'; import { defineStore } from 'pinia'; /** * antd使用 select和radio通用 * 本质上是对DictData的拓展 */ export interface DictOption extends DictData { disabled?: boolean; label: string; value: number | string; } /** * 将字典数据转为Options * @param data 字典数据 * @param formatNumber 是否需要将value格式化为number类型 * @returns options */ export function dictToOptions( data: DictData[], formatNumber = false, ): DictOption[] { return data.map((item) => ({ ...item, label: item.dictLabel, value: formatNumber ? Number(item.dictValue) : item.dictValue, })); } export const useDictStore = defineStore('app-dict', () => { /** * select radio checkbox等使用 只能为固定格式{label, value} */ const dictOptionsMap = reactive(new Map()); /** * 添加一个字典请求状态的缓存 * * 主要解决多次请求重复api的问题(不能用abortController 会导致除了第一个其他的获取的全为空) * 比如在一个页面 index表单 modal drawer总共会请求三次 但是获取的都是一样的数据 * 相当于加锁 保证只有第一次请求的结果能拿到 */ const dictRequestCache = reactive( new Map>(), ); function getDictOptions(dictName: string): DictOption[] { if (!dictName) return []; // 没有key 添加一个空数组 if (!dictOptionsMap.has(dictName)) { dictOptionsMap.set(dictName, []); } // 这里拿到的就不可能为空了 return dictOptionsMap.get(dictName)!; } function resetCache() { dictRequestCache.clear(); dictOptionsMap.clear(); /** * 不需要清空dictRequestCache 每次请求成功/失败都清空key */ } /** * 核心逻辑 * * 不能直接粗暴使用set 会导致之前return的空数组跟现在的数组指向不是同一个地址 数据也就为空了 * * 判断是否已经存在key 并且数组长度为0 说明该次要处理的数据是return的空数组 直接push(不修改指向) * 否则 直接set * */ function setDictInfo( dictName: string, dictValue: DictData[], formatNumber = false, ) { if ( dictOptionsMap.has(dictName) && dictOptionsMap.get(dictName)?.length === 0 ) { dictOptionsMap .get(dictName) ?.push(...dictToOptions(dictValue, formatNumber)); } else { dictOptionsMap.set(dictName, dictToOptions(dictValue, formatNumber)); } } function $reset() { /** * doNothing */ } return { $reset, dictOptionsMap, dictRequestCache, getDictOptions, resetCache, setDictInfo, }; }); ================================================ FILE: apps/web-antd/src/store/index.ts ================================================ export * from './auth'; export * from './notify'; ================================================ FILE: apps/web-antd/src/store/notify.ts ================================================ import type { NotificationItem } from '@vben/layouts'; import { computed, ref, watch } from 'vue'; import { SvgMessageUrl } from '@vben/icons'; import { $t } from '@vben/locales'; import { useUserStore } from '@vben/stores'; import { Modal, notification } from 'ant-design-vue'; import dayjs from 'dayjs'; import { defineStore } from 'pinia'; import { useSseMessage } from '#/utils/message'; export const useNotifyStore = defineStore( 'app-notify', () => { /** * return才会被持久化 存储全部消息 */ const notificationList = ref([]); const userStore = useUserStore(); const userId = computed(() => { return userStore.userInfo?.userId || '0'; }); const notifications = computed(() => { return notificationList.value.filter( (item) => item.userId === userId.value, ); }); /** * 开始监听sse消息 */ function startListeningMessage() { // 默认sse 使用 websocket自行开启注释 // const websocketReturnData = useWebSocketMessage(); // if (!websocketReturnData) { // return; // } // const { data } = websocketReturnData; const sseReturnData = useSseMessage(); if (!sseReturnData) { return; } const { data } = sseReturnData; watch(data, (message) => { if (!message) return; console.log(`接收到消息: ${message}`); notification.success({ description: message, duration: 3, message: $t('component.notice.received'), }); notificationList.value.unshift({ // avatar: `https://api.multiavatar.com/${random(0, 10_000)}.png`, 随机头像 avatar: SvgMessageUrl, date: dayjs().format('YYYY-MM-DD HH:mm:ss'), isRead: false, message, title: $t('component.notice.title'), userId: userId.value, }); // 需要手动置空 vue3在值相同时不会触发watch data.value = null; }); } /** * 设置全部已读 */ function setAllRead() { notificationList.value .filter((item) => item.userId === userId.value) .forEach((item) => { item.isRead = true; }); } /** * 设置单条消息已读 * @param item 通知 */ function setRead(item: NotificationItem) { !item.isRead && (item.isRead = true); // 显示信息 Modal.info({ title: item.title, content: item.message, }); } /** * 清空全部消息 */ function clearAllMessage() { notificationList.value = notificationList.value.filter( (item) => item.userId !== userId.value, ); } /** * 只需要空实现即可 * 否则会在退出登录清空所有 */ function $reset() { // notificationList.value = []; } /** * 显示小圆点 */ const showDot = computed(() => notificationList.value .filter((item) => item.userId === userId.value) .some((item) => !item.isRead), ); return { $reset, clearAllMessage, notificationList, notifications, setAllRead, setRead, showDot, startListeningMessage, }; }, { persist: { pick: ['notificationList'], }, }, ); ================================================ FILE: apps/web-antd/src/store/tenant.ts ================================================ import type { TenantOption } from '#/api/core/auth'; import { ref } from 'vue'; import { defineStore } from 'pinia'; import { tenantList as tenantListApi } from '#/api/core/auth'; /** * 用于超级管理员切换租户 */ export const useTenantStore = defineStore('app-tenant', () => { // 是否已经选中租户 const checked = ref(false); // 是否开启租户功能 const tenantEnable = ref(true); const tenantList = ref([]); // 初始化 获取租户信息 async function initTenant() { const { tenantEnabled, voList } = await tenantListApi(); tenantEnable.value = tenantEnabled; tenantList.value = voList; } async function setChecked(_checked: boolean) { checked.value = _checked; } function $reset() { checked.value = false; tenantEnable.value = true; tenantList.value = []; } return { $reset, checked, initTenant, setChecked, tenantEnable, tenantList, }; }); ================================================ FILE: apps/web-antd/src/upload-tip.ts ================================================ import { onMounted } from 'vue'; import { useLocalStorage } from '@vueuse/core'; import { Modal } from 'ant-design-vue'; export function useUploadTip() { const readTip = useLocalStorage('__upload_tip_read_5.4.0', false); onMounted(() => { if (readTip.value || !import.meta.env.DEV) { return; } const modalInstance = Modal.info({ title: '提示', centered: true, content: '如果你的版本是从低版本升级到后端>5.4.0, 记得执行升级sql, 否则跳转页面(如oss 代码生成配置)等会404', okButtonProps: { disabled: true }, onOk() { modalInstance.destroy(); readTip.value = true; }, }); let time = 3; const interval = setInterval(() => { modalInstance.update({ okText: time === 0 ? '我知道了, 不再弹出' : `${time}秒后关闭`, okButtonProps: { disabled: time > 0 }, }); if (time <= 0) { clearInterval(interval); } time--; }, 1000); }); } ================================================ FILE: apps/web-antd/src/utils/dict.ts ================================================ import { UnauthorizedException } from '#/api/helper'; import { dictDataInfo } from '#/api/system/dict/dict-data'; import { useDictStore } from '#/store/dict'; /** * 一般是Select, Radio, Checkbox等组件使用 * @warning 注意内部为异步实现 所以不要写这种`getDictOptions()[0]`的代码 会获取不到 * @warning 需要保持`formatNumber`统一 在所有调用地方需要一致 不能出现A处为string B处为number * * @param dictName 字典名称 * @param formatNumber 是否格式化字典value为number类型 * @returns Options数组 */ export function getDictOptions(dictName: string, formatNumber = false) { const { dictRequestCache, setDictInfo, getDictOptions } = useDictStore(); const dataList = getDictOptions(dictName); // 检查请求状态缓存 if (dataList.length === 0 && !dictRequestCache.has(dictName)) { dictRequestCache.set( dictName, dictDataInfo(dictName) .then((resp) => { // 缓存到store 这样就不用重复获取了 // 内部处理了push的逻辑 这里不用push setDictInfo(dictName, resp, formatNumber); }) .catch((error) => { /** * 需要判断是否为401抛出的特定异常 401清除缓存 * 其他error清除缓存会导致无限循环调用字典接口 则不做处理 */ if (error instanceof UnauthorizedException) { // 401时 移除字典缓存 下次登录重新获取 dictRequestCache.delete(dictName); } // 其他不做处理 }) .finally(() => { // 移除请求状态缓存 /** * 这里主要判断字典item为空的情况(无奈兼容 不给字典item本来就是错误用法) * 会导致if一直进入逻辑导致接口无限刷新 * 在这里dictList为空时 不删除缓存 */ if (dataList.length > 0) { dictRequestCache.delete(dictName); } }), ); } return dataList; } ================================================ FILE: apps/web-antd/src/utils/file/base64Conver.ts ================================================ /** * @description: base64 to blob */ export function dataURLtoBlob(base64Buf: string): Blob { const arr = base64Buf.split(','); const typeItem = arr[0]; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const mime = typeItem!.match(/:(.*?);/)![1]; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const bstr = window.atob(arr[1]!); let n = bstr.length; const u8arr = new Uint8Array(n); while (n--) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion u8arr[n] = bstr.codePointAt(n)!; } return new Blob([u8arr], { type: mime }); } /** * img url to base64 * @param url */ export function urlToBase64(url: string, mineType?: string): Promise { return new Promise((resolve, reject) => { let canvas = document.createElement('CANVAS') as HTMLCanvasElement | null; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const ctx = canvas!.getContext('2d'); const img = new Image(); img.crossOrigin = ''; img.addEventListener('load', () => { if (!canvas || !ctx) { // eslint-disable-next-line prefer-promise-reject-errors return reject(); } canvas.height = img.height; canvas.width = img.width; ctx.drawImage(img, 0, 0); const dataURL = canvas.toDataURL(mineType || 'image/png'); canvas = null; resolve(dataURL); }); img.src = url; }); } ================================================ FILE: apps/web-antd/src/utils/file/download.ts ================================================ import type { VbenFormProps } from '#/adapter/form'; import { $t } from '@vben/locales'; import { cloneDeep, formatDate } from '@vben/utils'; import { message } from 'ant-design-vue'; import { isFunction } from 'lodash-es'; import { dataURLtoBlob, urlToBase64 } from './base64Conver'; /** * * @deprecated 无法处理区间选择器数据 请使用commonDownloadExcel * * 下载excel文件 * @param [func] axios函数 * @param [fileName] 文件名称 不需要带xlsx后缀 * @param [requestData] 请求参数 * @param [withRandomName] 是否带随机文件名 * * @return void */ export async function downloadExcel( func: (data?: any) => Promise, fileName: string, requestData: any = {}, withRandomName = true, ) { const hideLoading = message.loading($t('pages.common.downloadLoading'), 0); try { const data = await func(requestData); downloadExcelFile(data, fileName, withRandomName); } catch (error) { console.error(error); } finally { hideLoading(); } } /** * 源码同packages\@core\ui-kit\form-ui\src\components\form-actions.vue * @param values 表单值 * @param fieldMappingTime 区间选择器 字段映射 * @returns 格式化后的值 */ function handleRangeTimeValue( values: Record, fieldMappingTime: VbenFormProps['fieldMappingTime'], ) { // 需要深拷贝 可能是readonly的 values = cloneDeep(values); if (!fieldMappingTime || !Array.isArray(fieldMappingTime)) { return values; } fieldMappingTime.forEach( ([field, [startTimeKey, endTimeKey], format = 'YYYY-MM-DD']) => { if (startTimeKey && endTimeKey && values[field] === null) { Reflect.deleteProperty(values, startTimeKey); Reflect.deleteProperty(values, endTimeKey); // delete values[startTimeKey]; // delete values[endTimeKey]; } if (!values[field]) { Reflect.deleteProperty(values, field); // delete values[field]; return; } const [startTime, endTime] = values[field]; if (format === null) { values[startTimeKey] = startTime; values[endTimeKey] = endTime; } else if (isFunction(format)) { values[startTimeKey] = format(startTime, startTimeKey); values[endTimeKey] = format(endTime, endTimeKey); } else { const [startTimeFormat, endTimeFormat] = Array.isArray(format) ? format : [format, format]; values[startTimeKey] = startTime ? formatDate(startTime, startTimeFormat) : undefined; values[endTimeKey] = endTime ? formatDate(endTime, endTimeFormat) : undefined; } // delete values[field]; Reflect.deleteProperty(values, field); }, ); return values; } export interface DownloadExcelOptions { // 是否随机文件名(带时间戳) withRandomName?: boolean; // 区间选择器 字段映射 fieldMappingTime?: VbenFormProps['fieldMappingTime']; } /** * 通用下载excel方法 * @param api 后端下载接口 * @param fileName 文件名 不带拓展名 * @param requestData 请求参数 * @param options 下载选项 */ export async function commonDownloadExcel( api: (data?: any) => Promise, fileName: string, requestData: any = {}, options: DownloadExcelOptions = {}, ) { const hideLoading = message.loading($t('pages.common.downloadLoading'), 0); try { const { withRandomName = true, fieldMappingTime } = options; // 需要处理时间字段映射 const data = await api(handleRangeTimeValue(requestData, fieldMappingTime)); downloadExcelFile(data, fileName, withRandomName); } catch (error) { console.error(error); } finally { hideLoading(); } } export function downloadExcelFile( data: BlobPart, filename: string, withRandomName = true, ) { let realFileName = filename; if (withRandomName) { realFileName = `${filename}-${Date.now()}.xlsx`; } downloadByData(data, realFileName); } /** * Download online pictures * @param url * @param filename * @param mime * @param bom */ export function downloadByOnlineUrl( url: string, filename: string, mime?: string, bom?: BlobPart, ) { urlToBase64(url).then((base64) => { downloadByBase64(base64, filename, mime, bom); }); } /** * Download pictures based on base64 * @param buf * @param filename * @param mime * @param bom */ export function downloadByBase64( buf: string, filename: string, mime?: string, bom?: BlobPart, ) { const base64Buf = dataURLtoBlob(buf); downloadByData(base64Buf, filename, mime, bom); } /** * Download according to the background interface file stream * @param {*} data * @param {*} filename * @param {*} mime * @param {*} bom */ export function downloadByData( data: BlobPart, filename: string, mime?: string, bom?: BlobPart, ) { const blobData = bom === undefined ? [data] : [bom, data]; const blob = new Blob(blobData, { type: mime || 'application/octet-stream' }); const blobURL = window.URL.createObjectURL(blob); const tempLink = document.createElement('a'); tempLink.style.display = 'none'; tempLink.href = blobURL; tempLink.setAttribute('download', filename); if (tempLink.download === undefined) { tempLink.setAttribute('target', '_blank'); } document.body.append(tempLink); tempLink.click(); tempLink.remove(); window.URL.revokeObjectURL(blobURL); } export function openWindow( url: string, opt?: { noopener?: boolean; noreferrer?: boolean; target?: '_blank' | '_self' | string; }, ) { const { noopener = true, noreferrer = true, target = '__blank' } = opt || {}; const feature: string[] = []; noopener && feature.push('noopener=yes'); noreferrer && feature.push('noreferrer=yes'); window.open(url, target, feature.join(',')); } /** * Download file according to file address * @param {*} sUrl */ export function downloadByUrl({ fileName, target = '_blank', url, }: { fileName?: string; target?: '_blank' | '_self'; url: string; }): boolean { const isChrome = window.navigator.userAgent.toLowerCase().includes('chrome'); const isSafari = window.navigator.userAgent.toLowerCase().includes('safari'); if (/iP/.test(window.navigator.userAgent)) { console.error('Your browser does not support download!'); return false; } if (isChrome || isSafari) { const link = document.createElement('a'); link.href = url; link.target = target; if (link.download !== undefined) { link.download = // eslint-disable-next-line unicorn/prefer-string-slice fileName || url.substring(url.lastIndexOf('/') + 1, url.length); } if (document.createEvent) { const e = document.createEvent('MouseEvents'); e.initEvent('click', true, true); link.dispatchEvent(e); return true; } } if (!url.includes('?')) { url += '?download'; } openWindow(url, { target }); return true; } ================================================ FILE: apps/web-antd/src/utils/file/index.ts ================================================ /** * 计算文件大小并以适当单位表示 * * 此函数接收一个表示文件大小的数字(以字节为单位),并返回一个格式化后的字符串, * 该字符串表示文件的大小,以最适合的单位(B, KB, MB, GB, TB)表示 * * @param size 文件大小,以字节为单位 * @param isInteger 是否返回整数大小,默认为false如果设置为true, * 则返回的大小将不包含小数部分;如果为false,则根据单位的不同, * 返回最多3位小数的大小 * @returns 格式化后的文件大小字符串,如"4.5KB"或"3MB" */ export function calculateFileSize(size: number, isInteger = false) { // 定义文件大小的单位数组 const units = ['B', 'KB', 'MB', 'GB', 'TB']; // 定义换算基数,1KB = 1024B,1MB = 1024KB,以此类推 const base = 1024; // 初始化单位索引,初始值为0,即默认单位为B let unitIndex = 0; // 当文件大小大于等于基数且单位索引未超出单位数组范围时,循环进行单位转换 while (size >= base && unitIndex < units.length - 1) { size /= base; unitIndex++; } // 根据是否需要整数大小,确定输出的精度 const precision = isInteger ? 0 : Math.min(unitIndex, 3); // 返回格式化后的文件大小字符串 return `${size.toFixed(precision)}${units[unitIndex]}`; } ================================================ FILE: apps/web-antd/src/utils/message.ts ================================================ import { useAppConfig } from '@vben/hooks'; import { useAccessStore } from '@vben/stores'; import { useEventSource, useWebSocket } from '@vueuse/core'; const { apiURL, clientId, sseEnable, websocketEnable } = useAppConfig( import.meta.env, import.meta.env.PROD, ); export function useSseMessage() { /** * 未开启 不监听 */ if (!sseEnable) { console.warn('当前未开启sse.'); return; } const accessStore = useAccessStore(); const token = accessStore.accessToken; const sseAddr = `${apiURL}/resource/sse?clientid=${clientId}&Authorization=Bearer ${token}`; const sseReturnData = useEventSource(sseAddr, [], { autoReconnect: { delay: 1000, onFailed() { console.error('sse重连失败.'); }, retries: 3, }, }); return sseReturnData; } function isUrl(path?: string) { return /^https?:\/\//.test(path || ''); } export function useWebSocketMessage() { if (!websocketEnable) { console.warn('当前未开启websocket.'); return; } let apiUrlStr = String(apiURL); /** * 这里可能有两种情况 兼容dev模式的proxy或者prod模式但是没有用全路径比如http://xxx/xxx * 1. apiUrl为https://xxx.com/xxx * 2. apiUrl为/xxx * 转换后为http链接形式 */ if (!isUrl(apiURL)) { // 协议+域名 apiUrlStr = `${window.location.protocol}//${window.location.host}${apiURL}`; } const accessStore = useAccessStore(); const token = accessStore.accessToken; // 这里是http链接形式 let websocketAddr = `${apiUrlStr}/resource/websocket?clientid=${clientId}&Authorization=Bearer ${token}`; // http/https处理 websocketAddr = window.location.protocol.includes('https') ? websocketAddr.replace('https://', 'wss://') : websocketAddr.replace('http://', 'ws://'); // console.log('websocketUrl: ' + websocketAddr); const websocketResponse = useWebSocket(websocketAddr, { autoReconnect: { // 重连最大次数 retries: 3, // 重连间隔 delay: 1000, onFailed() { console.error('websocket重连失败.'); }, }, heartbeat: { message: JSON.stringify({ type: 'ping' }), // 发送心跳的间隔 interval: 10_000, // 接收到心跳response的超时时间 pongTimeout: 2000, }, onConnected() { console.info('websocket已经连接'); }, onDisconnected() { console.warn('websocket已经断开'); }, }); return websocketResponse; } ================================================ FILE: apps/web-antd/src/utils/modal.tsx ================================================ import type { ModalFuncProps } from 'ant-design-vue'; import type { Rule } from 'ant-design-vue/es/form'; import { reactive } from 'vue'; import { Alert, Form, Input, Modal } from 'ant-design-vue'; import { isFunction } from 'lodash-es'; export interface ConfirmModalProps extends Omit { confirmText?: string; placeholder?: string; onValidated?: () => Promise; } export function confirmDeleteModal(props: ConfirmModalProps) { const placeholder = props.placeholder || `输入'确认删除'`; const confirmText = props.confirmText || '确认删除'; const formValue = reactive({ content: '', }); const rulesRef = reactive<{ [key: string]: Rule[] }>({ content: [ { message: '校验不通过', required: true, trigger: 'change', validator(_, value) { if (value !== confirmText) { return Promise.reject(new Error('校验不通过')); } return Promise.resolve(); }, }, ], }); const useForm = Form.useForm; const { validate, validateInfos } = useForm(formValue, rulesRef); Modal.confirm({ ...props, centered: true, content: (
), okButtonProps: { danger: true, type: 'primary' }, onOk: async () => { await validate(); isFunction(props.onValidated) && props.onValidated(); }, title: '提示', type: 'warning', }); } ================================================ FILE: apps/web-antd/src/utils/popup.ts ================================================ import type { ExtendedFormApi } from '@vben/common-ui'; import type { MaybePromise } from '@vben/types'; import { ref } from 'vue'; import { $t } from '@vben/locales'; import { Modal } from 'ant-design-vue'; import { isFunction } from 'lodash-es'; interface BeforeCloseDiffProps { /** * 初始化值如何获取 * @returns Promise */ initializedGetter: () => MaybePromise; /** * 当前值如何获取 * @returns Promise */ currentGetter: () => MaybePromise; /** * 自定义比较函数 * @param init 初始值 * @param current 当前值 * @returns boolean */ compare?: (init: string, current: string) => boolean; } /** * 用于Drawer/Modal使用 判断表单是否有变动来决定是否弹窗提示 * @param props props * @returns hook */ export function useBeforeCloseDiff(props: BeforeCloseDiffProps) { const { initializedGetter, currentGetter, compare } = props; /** * 记录初始值 json */ const initialized = ref(''); /** * 是否已经初始化了 通过这个值判断是否需要进行对比 为false直接关闭 不弹窗 */ const isInitialized = ref(false); /** * 标记是否已经完成初始化 后续需要进行对比 * @param data 自定义初始化数据 可选 */ async function markInitialized(data?: string) { initialized.value = data || (await initializedGetter()); isInitialized.value = true; } /** * 重置初始化状态 需要在closed前调用 或者打开窗口时 */ function resetInitialized() { initialized.value = ''; isInitialized.value = false; } /** * 提供给useVbenForm/useVbenDrawer使用 * @returns 是否允许关闭 */ async function onBeforeClose(): Promise { // 如果还未初始化,直接允许关闭 if (!isInitialized.value) { return true; } try { // 获取当前表单数据 const current = await currentGetter(); // 自定义比较的情况 if (isFunction(compare) && compare(initialized.value, current)) { return true; } else { // 如果数据没有变化,直接允许关闭 if (current === initialized.value) { return true; } } // 数据有变化,显示确认对话框 return new Promise((resolve) => { Modal.confirm({ title: $t('pages.common.tip'), content: $t('pages.common.beforeCloseTip'), centered: true, okButtonProps: { danger: true }, cancelText: $t('common.cancel'), okText: $t('common.confirm'), onOk: () => { resolve(true); isInitialized.value = false; }, onCancel: () => resolve(false), }); }); } catch (error) { console.error('Failed to compare data:', error); return true; } } return { onBeforeClose, markInitialized, resetInitialized, }; } /** * 给useVbenForm使用的 封装函数 * @param formApi 表单实例 * @returns getter */ export function defaultFormValueGetter(formApi: ExtendedFormApi) { return async () => { const v = await formApi.getValues(); return JSON.stringify(v); }; } ================================================ FILE: apps/web-antd/src/utils/render.tsx ================================================ import type { Component as ComponentType } from 'vue'; import type { DictData } from '#/api/system/dict/dict-data-model'; import type { DictFallback } from '#/components/dict/src/type'; import { h } from 'vue'; import { JsonPreview } from '@vben/common-ui'; import { AndroidIcon, BaiduIcon, ChromeIcon, DefaultBrowserIcon, DefaultOsIcon, DingtalkIcon, EdgeIcon, FirefoxIcon, IconifyIcon, IPhoneIcon, LinuxIcon, MicromessengerIcon, OperaIcon, OSXIcon, QuarkIcon, SafariIcon, SvgQQIcon, UcIcon, WindowsIcon, } from '@vben/icons'; import { Tag } from 'ant-design-vue'; import { DictTag } from '#/components/dict'; import { getDictOptions } from './dict'; /** * 渲染标签 * @param text 文字 * @param color 颜色 * @returns render */ function renderTag(text: string, color?: string) { return {text}; } /** * * @param tags 标签list * @param wrap 是否换行显示 * @param [gap] 间隔 * @returns render */ export function renderTags(tags: string[], wrap = false, gap = 1) { return (
{tags.map((tag, index) => { return
{renderTag(tag)}
; })}
); } /** * * @param json json对象 接受object/string类型 * @returns json预览 */ export function renderJsonPreview(json: any) { if (typeof json !== 'object' && typeof json !== 'string') { return {json}; } if (typeof json === 'object') { return ; } try { const obj = JSON.parse(json); // 基本数据类型可以被转为json if (typeof obj !== 'object') { return {obj}; } return ; } catch { return {json}; } } /** * iconify图标 * @param icon icon名称 * @returns render */ export function renderIcon(icon: string) { return ; } /** * httpMethod标签 * @param type method类型 * @returns render */ export function renderHttpMethodTag(type: string) { const method = type.toUpperCase(); const colors: { [key: string]: string } = { DELETE: 'red', GET: 'green', POST: 'blue', PUT: 'orange', }; const color = colors[method] ?? 'default'; const title = `${method}请求`; return {title}; } export function renderDictTag(value: number | string, dicts: DictData[]) { return ; } /** * render多个dictTag * @param value key数组 string[]类型 * @param dicts 字典数组 * @param wrap 是否需要换行显示 * @param [gap] 间隔 * @returns render */ export function renderDictTags( value: string[], dicts: DictData[], wrap = true, gap = 1, ) { if (!Array.isArray(value)) { return
{value}
; } return (
{value.map((item, index) => { return
{renderDictTag(item, dicts)}
; })}
); } export interface RenderDictOptions { fallback?: DictFallback; } /** * 显示字典标签 一般是table使用 * @param value 值 * @param dictName dictName * @returns tag */ export function renderDict( value: number | string, dictName: string, options?: RenderDictOptions, ) { const { fallback } = options ?? {}; const dictInfo = getDictOptions(dictName); return ; } export function renderIconSpan( icon: ComponentType, value: string, center = false, marginLeft = '2px', ) { const justifyCenter = center ? 'justify-center' : ''; return ( {h(icon)} {value} ); } const osOptions = [ { icon: WindowsIcon, value: 'windows' }, { icon: LinuxIcon, value: 'linux' }, { icon: OSXIcon, value: 'osx' }, { icon: AndroidIcon, value: 'android' }, { icon: IPhoneIcon, value: 'iphone' }, ]; /** * 浏览器图标 * cn.hutool.http.useragent -> browers */ const browserOptions = [ { icon: ChromeIcon, value: 'chrome' }, { icon: EdgeIcon, value: 'edge' }, { icon: FirefoxIcon, value: 'firefox' }, { icon: OperaIcon, value: 'opera' }, { icon: SafariIcon, value: 'safari' }, { icon: MicromessengerIcon, value: 'micromessenger' }, { icon: MicromessengerIcon, value: 'windowswechat' }, { icon: QuarkIcon, value: 'quark' }, { icon: MicromessengerIcon, value: 'wxwork' }, { icon: SvgQQIcon, value: 'qq' }, { icon: DingtalkIcon, value: 'dingtalk' }, { icon: UcIcon, value: 'uc' }, { icon: BaiduIcon, value: 'baidu' }, ]; export function renderOsIcon(os: string, center = false) { if (!os) { return; } let current = osOptions.find((item) => os.toLocaleLowerCase().includes(item.value), ); // windows要特殊处理 if (os.toLocaleLowerCase().includes('windows')) { current = osOptions[0]; } const icon = current ? current.icon : DefaultOsIcon; return renderIconSpan(icon, os, center, '5px'); } export function renderBrowserIcon(browser: string, center = false) { if (!browser) { return; } const current = browserOptions.find((item) => browser.toLocaleLowerCase().includes(item.value), ); const icon = current ? current.icon : DefaultBrowserIcon; return renderIconSpan(icon, browser, center, '5px'); } ================================================ FILE: apps/web-antd/src/views/_core/README.md ================================================ # \_core 此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图。 ================================================ FILE: apps/web-antd/src/views/_core/about/index.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/authentication/code-login.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/authentication/forget-password.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/authentication/login.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/authentication/oauth-login.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/authentication/qrcode-login.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/authentication/register.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/fallback/coming-soon.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/fallback/forbidden.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/fallback/internal-error.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/fallback/not-found.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/fallback/offline.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/oauth-common.ts ================================================ import type { Component, CSSProperties } from 'vue'; import { markRaw, ref } from 'vue'; import { DEFAULT_TENANT_ID } from '@vben/constants'; import { GiteeIcon, GithubOAuthIcon, SvgMaxKeyIcon, SvgTopiamIcon, SvgWechatIcon, } from '@vben/icons'; import { createGlobalState } from '@vueuse/core'; import { authBinding } from '#/api/core/auth'; /** * @description: oauth登录 * @param title 标题 * @param description 描述 * @param avatar 图标 * @param color 图标颜色可直接写英文颜色/hex */ export interface ListItem { title: string; description: string; avatar?: Component; style?: CSSProperties; } /** * @description: 绑定账号 * @param source 来源 如gitee github 与后端的social-callback?source=xxx对应 * @param bound 是否已经绑定 */ export interface BindItem extends ListItem { source: string; bound?: boolean; } /** * 这里存储登录页的tenantId 由于个人中心也会用到 需要共享 * 所以使用`createGlobalState` * @see https://vueuse.org/shared/createGlobalState/ */ export const useLoginTenantId = createGlobalState(() => { const loginTenantId = ref(DEFAULT_TENANT_ID); return { loginTenantId, }; }); /** * 绑定授权 * @param source */ export async function handleAuthBinding(source: string) { const { loginTenantId } = useLoginTenantId(); // 这里返回打开授权页面的链接 const href = await authBinding(source, loginTenantId.value); window.location.href = href; } /** * 账号绑定 list * 添加账号绑定只需要在这里增加即可 */ export const accountBindList: BindItem[] = [ { avatar: markRaw(GiteeIcon), description: '绑定Gitee账号', source: 'gitee', title: 'Gitee', style: { color: '#c71d23' }, }, { avatar: markRaw(GithubOAuthIcon), description: '绑定Github账号', source: 'github', title: 'Github', }, { avatar: markRaw(SvgMaxKeyIcon), description: '绑定MaxKey账号', source: 'maxkey', title: 'MaxKey', }, { avatar: markRaw(SvgTopiamIcon), description: '绑定topiam账号', source: 'topiam', title: 'Topiam', }, { avatar: markRaw(SvgWechatIcon), description: '绑定wechat账号', source: 'wechat', title: 'Wechat', }, ]; ================================================ FILE: apps/web-antd/src/views/_core/profile/components/account-bind.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/profile/components/base-setting.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/profile/components/online-device.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/profile/components/secure-setting.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/profile/index.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/profile/mitt.ts ================================================ import { mitt } from '@vben/utils'; type Events = { updateProfile: void; }; export const emitter = mitt(); ================================================ FILE: apps/web-antd/src/views/_core/profile/profile-panel.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/profile/setting-panel.vue ================================================ ================================================ FILE: apps/web-antd/src/views/_core/social-callback/index.vue ================================================ ================================================ FILE: apps/web-antd/src/views/aiflow/README.md ================================================ # 工作流管理模块 ## 📁 目录结构 ``` workflow/ ├── index.vue # 工作流列表页面(主页面) ├── edit.vue # 工作流编辑页面 ├── run.vue # 工作流运行页面 ├── data.ts # 表格列定义和查询表单配置 └── README.md # 本文档 ``` ## 🎯 页面说明 ### 1. 工作流列表页面 (index.vue) **路由:** `/workflow` **功能:** - ✅ 展示所有工作流(我的工作流和公开工作流) - ✅ 搜索和过滤工作流 - ✅ 新建工作流 - ✅ 编辑、运行、删除工作流 - ✅ 批量删除工作流 - ✅ 分页展示 **设计风格:** 参考系统模型页面,使用VxeGrid表格 ### 2. 工作流编辑页面 (edit.vue) **路由:** `/workflow/edit/:uuid` **功能:** - ✅ 加载指定工作流数据 - ✅ 可视化编辑工作流(拖拽节点、连接线等) - ✅ 保存工作流(保存后自动返回列表页) - ✅ 取消编辑(提示确认,返回列表页) - ✅ 显示页面标题和返回按钮 **组件:** 使用 `WorkflowDesigner` 组件进行编辑 ### 3. 工作流运行页面 (run.vue) **路由:** `/workflow/run/:uuid` **功能:** - ✅ 加载工作流数据 - ✅ 运行工作流 - ✅ 展示运行结果和日志 - ✅ 支持人工反馈 **组件:** 使用 `RunDetail` 组件展示运行详情 ## 🔄 业务流程 ### 新建工作流 ``` 列表页 -> 点击"新建工作流" -> 后台创建 -> 跳转到编辑页 -> 编辑 -> 保存 -> 返回列表页 ``` ### 编辑工作流 ``` 列表页 -> 点击"编辑" -> 跳转到编辑页 -> 编辑 -> 保存 -> 返回列表页 ``` ### 运行工作流 ``` 列表页 -> 点击"运行" -> 跳转到运行页 -> 运行并展示结果 ``` ## 📊 数据结构 ### WorkflowInfo 工作流信息 ```typescript interface WorkflowInfo { uuid: string; // 工作流唯一标识 title: string; // 工作流名称 remark?: string; // 备注 isPublic: boolean; // 是否公开 nodes: WorkflowNode[]; // 节点列表 edges: WorkflowEdge[]; // 连线列表 createTime?: string; // 创建时间 updateTime?: string; // 更新时间 } ``` ## 🔌 API 接口 ### 列表页使用的接口 | 接口 | 方法 | 说明 | |------|------|------| | `workflowPage` | POST | 分页查询工作流列表 | | `workflowAdd` | POST | 新建工作流 | | `workflowUpdate` | POST | 更新工作流 | | `workflowDel` | POST | 删除工作流 | ### 编辑页使用的接口 | 接口 | 方法 | 说明 | |------|------|------| | `workflowGet` | GET | 获取工作流详情 | | `workflowUpdate` | POST | 保存工作流 | | `workflowComponents` | GET | 获取组件列表 | ### 运行页使用的接口 | 接口 | 方法 | 说明 | |------|------|------| | `workflowGet` | GET | 获取工作流详情 | | `workflowRuntimeResume` | POST | 恢复运行(人工反馈) | ## 🎨 样式风格 ### 列表页 - 使用 `Page` 组件包裹 - 使用 `VxeGrid` 表格展示数据 - 使用 `ghost-button` 按钮进行操作 - 搜索表单采用响应式布局(grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4) ### 编辑页 - 使用 `Page` 组件包裹,显示标题和返回按钮 - 全屏展示工作流设计器 - 设计器高度:`calc(100vh - 120px)` ### 运行页 - 使用 `Page` 组件包裹,显示标题和返回按钮 - 居中展示运行详情 - 最大宽度:800px ## 🚀 使用示例 ### 跳转到编辑页 ```typescript import { useRouter } from 'vue-router'; const router = useRouter(); // 编辑现有工作流 router.push({ name: 'WorkflowEdit', params: { uuid: workflowUuid }, }); ``` ### 跳转到运行页 ```typescript // 运行工作流 router.push({ name: 'WorkflowRun', params: { uuid: workflowUuid }, }); ``` ### 返回列表页 ```typescript // 从编辑页或运行页返回 router.push({ name: 'Workflow' }); ``` ## ⚠️ 注意事项 1. **保存自动返回:** 编辑页保存成功后会自动返回列表页 2. **取消提示:** 点击返回按钮会提示确认,防止误操作导致数据丢失 3. **加载状态:** 编辑页和运行页在加载数据时会显示loading状态 4. **错误处理:** 所有API调用都包含错误处理,失败时会显示错误消息 ## 📝 TODO - [ ] 添加工作流模板功能 - [ ] 支持工作流导入导出 - [ ] 添加工作流版本管理 - [ ] 优化运行日志展示 ================================================ FILE: apps/web-antd/src/views/aiflow/data.ts ================================================ import type { FormSchemaGetter } from '#/adapter/form'; import type { VxeGridProps } from '#/adapter/vxe-table'; import { h } from 'vue'; import { Tag } from 'ant-design-vue'; // import { getPopupContainer } from '@vben/utils'; export const querySchema: FormSchemaGetter = () => [ { component: 'Input', fieldName: 'title', label: '工作流名称', }, // { // component: 'Select', // componentProps: { // getPopupContainer, // options: [ // { label: '全部', value: '' }, // { label: '我的', value: 'my' }, // { label: '公开', value: 'public' }, // ], // }, // fieldName: 'type', // label: '类型', // }, ]; export const columns: VxeGridProps['columns'] = [ { type: 'checkbox', width: 60 }, { title: '工作流名称', field: 'title', minWidth: 180, }, { title: '备注', field: 'remark', minWidth: 200, }, { title: '节点数量', field: 'nodeCount', width: 100, slots: { default: ({ row }) => { const count = Array.isArray(row.nodes) ? row.nodes.length : 0; return h('span', count.toString()); }, }, }, { title: '是否公开', field: 'isPublic', width: 100, slots: { default: ({ row }) => { if (row.isPublic) { return h(Tag, { color: 'green' }, () => '公开'); } return h(Tag, { color: 'default' }, () => '私有'); }, }, }, { title: '创建时间', field: 'createTime', width: 180, }, { title: '更新时间', field: 'updateTime', width: 180, }, { field: 'action', fixed: 'right', slots: { default: 'action' }, title: '操作', width: 320, }, ]; ================================================ FILE: apps/web-antd/src/views/aiflow/edit.vue ================================================ ================================================ FILE: apps/web-antd/src/views/aiflow/index.vue ================================================ ================================================ FILE: apps/web-antd/src/views/aiflow/run.vue ================================================ ================================================ FILE: apps/web-antd/src/views/aiflow/workflow-modal.vue ================================================ ================================================ FILE: apps/web-antd/src/views/chat/message/data.ts ================================================ import type { FormSchemaGetter } from '#/adapter/form'; import type { VxeGridProps } from '#/adapter/vxe-table'; export const querySchema: FormSchemaGetter = () => [ { component: 'Input', fieldName: 'modelName', label: '模型名称', }, ]; // 需要使用i18n注意这里要改成getter形式 否则切换语言不会刷新 // export const columns: () => VxeGridProps['columns'] = () => [ export const columns: VxeGridProps['columns'] = [ { type: 'checkbox', width: 60 }, { title: '主键', field: 'id', }, { title: '用户id', field: 'userId', }, { title: '消息内容', field: 'content', }, { title: '对话角色', field: 'role', }, { title: '累计 Tokens', field: 'totalTokens', }, { title: '模型名称', field: 'modelName', }, { title: '备注', field: 'remark', }, { field: 'action', fixed: 'right', slots: { default: 'action' }, title: '操作', width: 180, }, ]; ================================================ FILE: apps/web-antd/src/views/chat/message/index.vue ================================================ ================================================ FILE: apps/web-antd/src/views/chat/message/message-modal.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/textarea/index.ts ================================================ export { default as Textarea } from './Textarea.vue'; ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/toggle/Toggle.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/toggle/index.ts ================================================ export * from './toggle'; export { default as Toggle } from './Toggle.vue'; ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/toggle/toggle.ts ================================================ import type { VariantProps } from 'class-variance-authority'; import { cva } from 'class-variance-authority'; export const toggleVariants = cva( 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground', { defaultVariants: { size: 'default', variant: 'default', }, variants: { size: { default: 'h-9 px-3', lg: 'h-10 px-3', sm: 'h-8 px-2', }, variant: { default: 'bg-transparent', outline: 'border border-input bg-transparent shadow-sm hover:bg-accent hover:text-accent-foreground', }, }, }, ); export type ToggleVariants = VariantProps; ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/toggle-group/ToggleGroup.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/toggle-group/ToggleGroupItem.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/toggle-group/index.ts ================================================ export { default as ToggleGroup } from './ToggleGroup.vue'; export { default as ToggleGroupItem } from './ToggleGroupItem.vue'; ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/Tooltip.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/TooltipContent.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/TooltipProvider.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/TooltipTrigger.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tooltip/index.ts ================================================ export { default as Tooltip } from './Tooltip.vue'; export { default as TooltipContent } from './TooltipContent.vue'; export { default as TooltipProvider } from './TooltipProvider.vue'; export { default as TooltipTrigger } from './TooltipTrigger.vue'; ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tree/index.ts ================================================ export { default as VbenTree } from './tree.vue'; export type { TreeProps } from './types'; export { treePropsDefaults } from './types'; export type { FlattenedItem } from 'radix-vue'; ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tree/tree.vue ================================================ ================================================ FILE: packages/@core/ui-kit/shadcn-ui/src/ui/tree/types.ts ================================================ import type { Arrayable } from '@vueuse/core'; import type { FlattenedItem } from 'radix-vue'; import type { Recordable } from '@vben-core/typings'; export interface TreeProps { /** 单选时允许取消已有选项 */ allowClear?: boolean; /** 非关联选择时,自动选中上级节点 */ autoCheckParent?: boolean; /** 显示边框 */ bordered?: boolean; /** 取消父子关联选择 */ checkStrictly?: boolean; /** 子级字段名 */ childrenField?: string; /** 默认展开的键 */ defaultExpandedKeys?: Array; /** 默认展开的级别(优先级高于defaultExpandedKeys) */ defaultExpandedLevel?: number; /** 默认值 */ defaultValue?: Arrayable; /** 禁用 */ disabled?: boolean; /** 禁用字段名 */ disabledField?: string; /** 自定义节点类名 */ getNodeClass?: (item: FlattenedItem>) => string; iconField?: string; /** label字段 */ labelField?: string; /** 是否多选 */ multiple?: boolean; /** 显示由iconField指定的图标 */ showIcon?: boolean; /** 启用展开收缩动画 */ transition?: boolean; /** 树数据 */ treeData: Recordable[]; /** 值字段 */ valueField?: string; } export function treePropsDefaults() { return { allowClear: false, autoCheckParent: true, bordered: false, checkStrictly: false, defaultExpandedKeys: () => [], defaultExpandedLevel: 0, disabled: false, disabledField: 'disabled', iconField: 'icon', labelField: 'label', multiple: false, showIcon: true, transition: true, valueField: 'value', childrenField: 'children', }; } ================================================ FILE: packages/@core/ui-kit/shadcn-ui/tailwind.config.mjs ================================================ export { default } from '@vben/tailwind-config'; ================================================ FILE: packages/@core/ui-kit/shadcn-ui/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "compilerOptions": { "baseUrl": ".", "paths": { "@vben-core/shadcn-ui/*": ["./src/*"] } }, "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/@core/ui-kit/tabs-ui/build.config.ts ================================================ import { defineBuildConfig } from 'unbuild'; export default defineBuildConfig({ clean: true, declaration: true, entries: [ { builder: 'mkdist', input: './src', loaders: ['vue'], pattern: ['**/*.vue'], }, { builder: 'mkdist', format: 'esm', input: './src', loaders: ['js'], pattern: ['**/*.ts'], }, ], }); ================================================ FILE: packages/@core/ui-kit/tabs-ui/package.json ================================================ { "name": "@vben-core/tabs-ui", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/@vben-core/uikit/tabs-ui" }, "license": "MIT", "type": "module", "scripts": { "build": "pnpm unbuild", "prepublishOnly": "npm run build" }, "files": [ "dist" ], "sideEffects": [ "**/*.css" ], "main": "./dist/index.mjs", "module": "./dist/index.mjs", "exports": { ".": { "types": "./src/index.ts", "development": "./src/index.ts", "default": "./dist/index.mjs" } }, "publishConfig": { "exports": { ".": { "default": "./dist/index.mjs" } } }, "dependencies": { "@vben-core/composables": "workspace:*", "@vben-core/icons": "workspace:*", "@vben-core/shadcn-ui": "workspace:*", "@vben-core/typings": "workspace:*", "@vueuse/core": "catalog:", "vue": "catalog:" } } ================================================ FILE: packages/@core/ui-kit/tabs-ui/postcss.config.mjs ================================================ export { default } from '@vben/tailwind-config/postcss'; ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/components/index.ts ================================================ export { default as Tabs } from './tabs/tabs.vue'; export { default as TabsChrome } from './tabs-chrome/tabs.vue'; ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/components/tabs/tabs.vue ================================================ ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/components/tabs-chrome/tabs.vue ================================================ ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/components/widgets/index.ts ================================================ export { default as TabsToolMore } from './tool-more.vue'; export { default as TabsToolScreen } from './tool-screen.vue'; ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-more.vue ================================================ ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/components/widgets/tool-screen.vue ================================================ ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/index.ts ================================================ export * from './components/widgets'; export { default as TabsView } from './tabs-view.vue'; export type { IContextMenuItem } from '@vben-core/shadcn-ui'; ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/tabs-view.vue ================================================ ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/types.ts ================================================ import type { IContextMenuItem } from '@vben-core/shadcn-ui'; import type { TabDefinition, TabsStyleType } from '@vben-core/typings'; export type TabsEmits = { close: [string]; sortTabs: [number, number]; unpin: [TabDefinition]; }; export interface TabsProps { active?: string; /** * @zh_CN content class * @default tabs-chrome */ contentClass?: string; /** * @zh_CN 右键菜单 */ contextMenus?: (data: any) => IContextMenuItem[]; /** * @zh_CN 是否可以拖拽 */ draggable?: boolean; /** * @zh_CN 间隙 * @default 7 * 仅限 tabs-chrome */ gap?: number; /** * @zh_CN tab 最大宽度 * 仅限 tabs-chrome */ maxWidth?: number; /** * @zh_CN 点击中键时关闭Tab */ middleClickToClose?: boolean; /** * @zh_CN tab最小宽度 * 仅限 tabs-chrome */ minWidth?: number; /** * @zh_CN 是否显示图标 */ showIcon?: boolean; /** * @zh_CN 标签页风格 */ styleType?: TabsStyleType; /** * @zh_CN 选项卡数据 */ tabs?: TabDefinition[]; /** * @zh_CN 是否响应滚轮事件 */ wheelable?: boolean; } export interface TabConfig extends TabDefinition { affixTab: boolean; closable: boolean; icon: string; key: string; title: string; } ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/use-tabs-drag.ts ================================================ import type { Sortable } from '@vben-core/composables'; import type { EmitType } from '@vben-core/typings'; import type { TabsProps } from './types'; import { useIsMobile, useSortable } from '@vben-core/composables'; import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; // 可能会找到拖拽的子元素,这里需要确保拖拽的dom时tab元素 function findParentElement(element: HTMLElement) { const parentCls = 'group'; return element.classList.contains(parentCls) ? element : element.closest(`.${parentCls}`); } export function useTabsDrag(props: TabsProps, emit: EmitType) { const sortableInstance = ref(null); async function initTabsSortable() { await nextTick(); const el = document.querySelectorAll( `.${props.contentClass}`, )?.[0] as HTMLElement; if (!el) { console.warn('Element not found for sortable initialization'); return; } const resetElState = async () => { el.style.cursor = 'default'; // el.classList.remove('dragging'); el.querySelector('.draggable')?.classList.remove('dragging'); }; const { initializeSortable } = useSortable(el, { filter: (_evt, target: HTMLElement) => { const parent = findParentElement(target); const draggable = parent?.classList.contains('draggable'); return !draggable || !props.draggable; }, onEnd(evt) { const { newIndex, oldIndex } = evt; // const fromElement = evt.item; const { srcElement } = (evt as any).originalEvent; if (!srcElement) { resetElState(); return; } const srcParent = findParentElement(srcElement); if (!srcParent) { resetElState(); return; } if (!srcParent.classList.contains('draggable')) { resetElState(); return; } if ( oldIndex !== undefined && newIndex !== undefined && !Number.isNaN(oldIndex) && !Number.isNaN(newIndex) && oldIndex !== newIndex ) { emit('sortTabs', oldIndex, newIndex); } resetElState(); }, onMove(evt) { const parent = findParentElement(evt.related); if (parent?.classList.contains('draggable') && props.draggable) { const isCurrentAffix = evt.dragged.classList.contains('affix-tab'); const isRelatedAffix = evt.related.classList.contains('affix-tab'); // 不允许在固定的tab和非固定的tab之间互相拖拽 return isCurrentAffix === isRelatedAffix; } else { return false; } }, onStart: () => { el.style.cursor = 'grabbing'; el.querySelector('.draggable')?.classList.add('dragging'); // el.classList.add('dragging'); }, }); sortableInstance.value = await initializeSortable(); } async function init() { const { isMobile } = useIsMobile(); // 移动端下tab不需要拖拽 if (isMobile.value) { return; } await nextTick(); initTabsSortable(); } onMounted(init); watch( () => props.styleType, () => { sortableInstance.value?.destroy(); init(); }, ); onUnmounted(() => { sortableInstance.value?.destroy(); }); } ================================================ FILE: packages/@core/ui-kit/tabs-ui/src/use-tabs-view-scroll.ts ================================================ import type { TabsProps } from './types'; import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue'; import { VbenScrollbar } from '@vben-core/shadcn-ui'; import { useDebounceFn } from '@vueuse/core'; type DomElement = Element | null | undefined; export function useTabsViewScroll(props: TabsProps) { let resizeObserver: null | ResizeObserver = null; let mutationObserver: MutationObserver | null = null; let tabItemCount = 0; const scrollbarRef = ref | null>(null); const scrollViewportEl = ref(null); const showScrollButton = ref(false); const scrollIsAtLeft = ref(true); const scrollIsAtRight = ref(false); function getScrollClientWidth() { const scrollbarEl = scrollbarRef.value?.$el; if (!scrollbarEl || !scrollViewportEl.value) return {}; const scrollbarWidth = scrollbarEl.clientWidth; const scrollViewWidth = scrollViewportEl.value.clientWidth; return { scrollbarWidth, scrollViewWidth, }; } function scrollDirection( direction: 'left' | 'right', distance: number = 150, ) { const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth(); if (!scrollbarWidth || !scrollViewWidth) return; if (scrollbarWidth > scrollViewWidth) return; scrollViewportEl.value?.scrollBy({ behavior: 'smooth', left: direction === 'left' ? -(scrollbarWidth - distance) : +(scrollbarWidth - distance), }); } async function initScrollbar() { await nextTick(); const scrollbarEl = scrollbarRef.value?.$el; if (!scrollbarEl) { return; } const viewportEl = scrollbarEl?.querySelector( 'div[data-radix-scroll-area-viewport]', ); scrollViewportEl.value = viewportEl; calcShowScrollbarButton(); await nextTick(); scrollToActiveIntoView(); // 监听大小变化 resizeObserver?.disconnect(); resizeObserver = new ResizeObserver( useDebounceFn((_entries: ResizeObserverEntry[]) => { calcShowScrollbarButton(); scrollToActiveIntoView(); }, 100), ); resizeObserver.observe(viewportEl); tabItemCount = props.tabs?.length || 0; mutationObserver?.disconnect(); // 使用 MutationObserver 仅监听子节点数量变化 mutationObserver = new MutationObserver(() => { const count = viewportEl.querySelectorAll( `div[data-tab-item="true"]`, ).length; if (count > tabItemCount) { scrollToActiveIntoView(); } if (count !== tabItemCount) { calcShowScrollbarButton(); tabItemCount = count; } }); // 配置为仅监听子节点的添加和移除 mutationObserver.observe(viewportEl, { attributes: false, childList: true, subtree: true, }); } async function scrollToActiveIntoView() { if (!scrollViewportEl.value) { return; } await nextTick(); const viewportEl = scrollViewportEl.value; const { scrollbarWidth } = getScrollClientWidth(); const { scrollWidth } = viewportEl; if (scrollbarWidth >= scrollWidth) { return; } requestAnimationFrame(() => { const activeItem = viewportEl?.querySelector('.is-active'); activeItem?.scrollIntoView({ behavior: 'smooth', inline: 'start' }); }); } /** * 计算tabs 宽度,用于判断是否显示左右滚动按钮 */ async function calcShowScrollbarButton() { if (!scrollViewportEl.value) { return; } const { scrollbarWidth } = getScrollClientWidth(); showScrollButton.value = scrollViewportEl.value.scrollWidth > scrollbarWidth; } const handleScrollAt = useDebounceFn(({ left, right }) => { scrollIsAtLeft.value = left; scrollIsAtRight.value = right; }, 100); function handleWheel({ deltaY }: WheelEvent) { scrollViewportEl.value?.scrollBy({ // behavior: 'smooth', left: deltaY * 3, }); } watch( () => props.active, async () => { // 200为了等待 tab 切换动画完成 // setTimeout(() => { scrollToActiveIntoView(); // }, 300); }, { flush: 'post', }, ); // watch( // () => props.tabs?.length, // async () => { // await nextTick(); // calcShowScrollbarButton(); // }, // { // flush: 'post', // }, // ); watch( () => props.styleType, () => { initScrollbar(); }, ); onMounted(initScrollbar); onUnmounted(() => { resizeObserver?.disconnect(); mutationObserver?.disconnect(); resizeObserver = null; mutationObserver = null; }); return { handleScrollAt, handleWheel, initScrollbar, scrollbarRef, scrollDirection, scrollIsAtLeft, scrollIsAtRight, showScrollButton, }; } ================================================ FILE: packages/@core/ui-kit/tabs-ui/tailwind.config.mjs ================================================ export { default } from '@vben/tailwind-config'; ================================================ FILE: packages/@core/ui-kit/tabs-ui/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/constants/README.md ================================================ # @vben/constants 用于多个 `app` 公用的常量,继承了 `@vben-core/shared/constants` 的所有能力。业务上有通用常量可以放在这里。 ## 用法 ### 添加依赖 ```bash # 进入目标应用目录,例如 apps/xxxx-app # cd apps/xxxx-app pnpm add @vben/constants ``` ### 使用 ```ts import { LOGIN_PATH } from '@vben/constants'; ``` ================================================ FILE: packages/constants/package.json ================================================ { "name": "@vben/constants", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/constants" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben-core/shared": "workspace:*" } } ================================================ FILE: packages/constants/src/core.ts ================================================ /** * @zh_CN 登录页面 url 地址 */ export const LOGIN_PATH = '/auth/login'; export interface LanguageOption { label: string; value: 'en-US' | 'zh-CN'; } /** * Supported languages */ export const SUPPORT_LANGUAGES: LanguageOption[] = [ { label: '简体中文', value: 'zh-CN', }, { label: 'English', value: 'en-US', }, ]; /** * 默认租户ID */ export const DEFAULT_TENANT_ID = '000000'; /** * 业务成功 状态码 */ export const BUSINESS_SUCCESS_CODE = 200; /** * 未授权 状态码(登录超时) */ export const UNAUTHORIZED_CODE = 401; ================================================ FILE: packages/constants/src/index.ts ================================================ export * from './core'; export * from '@vben-core/shared/constants'; ================================================ FILE: packages/constants/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/effects/README.md ================================================ ## Effects 目录 `effects` 目录专门用于存放与轻微耦合相关的代码和逻辑。如果你的包具有以下特点,建议将其放置在 `effects` 目录下: - **状态管理**:使用状态管理框架 `pinia`,并包含处理副作用(如异步操作、API 调用)的部分。 - **用户偏好设置**:使用 `@vben-core/preferences` 处理用户偏好设置,涉及本地存储或浏览器缓存逻辑(如使用 `localStorage`)。 - **导航和路由**:处理导航、页面跳转等场景,需要管理路由变化的逻辑。 - **组件库依赖**:包含与特定组件库紧密耦合或依赖大型仓库的部分。 通过将相关代码归类到 `effects` 目录,可以使项目结构更加清晰,便于维护和扩展。 ================================================ FILE: packages/effects/access/package.json ================================================ { "name": "@vben/access", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/effects/permissions" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben/preferences": "workspace:*", "@vben/stores": "workspace:*", "@vben/types": "workspace:*", "@vben/utils": "workspace:*", "vue": "catalog:" } } ================================================ FILE: packages/effects/access/src/access-control.vue ================================================ ================================================ FILE: packages/effects/access/src/accessible.ts ================================================ import type { Component, DefineComponent } from 'vue'; import type { AccessModeType, GenerateMenuAndRoutesOptions, RouteRecordRaw, } from '@vben/types'; import { defineComponent, h } from 'vue'; import { cloneDeep, generateMenus, generateRoutesByBackend, generateRoutesByFrontend, isFunction, isString, mapTree, setObjToUrlParams, } from '@vben/utils'; async function generateAccessible( mode: AccessModeType, options: GenerateMenuAndRoutesOptions, ) { const { router } = options; options.routes = cloneDeep(options.routes); // 生成路由 const accessibleRoutes = await generateRoutes(mode, options); const root = router.getRoutes().find((item) => item.path === '/'); // 获取已有的路由名称列表 const names = root?.children?.map((item) => item.name) ?? []; // 动态添加到router实例内 accessibleRoutes.forEach((route) => { /** * 外链不应该被添加到路由 由menu处理 */ if (/^https?:\/\//.test(route.path)) { return; } if (root && !route.meta?.noBasicLayout) { // 为了兼容之前的版本用法,如果包含子路由,则将component移除,以免出现多层BasicLayout // 如果你的项目已经跟进了本次修改,移除了所有自定义菜单首级的BasicLayout,可以将这段if代码删除 // TODO: 这里后期需要follow更新 if (route.children && route.children.length > 0) { delete route.component; } // 根据router name判断,如果路由已经存在,则不再添加 if (names?.includes(route.name)) { // 找到已存在的路由索引并更新,不更新会造成切换用户时,一级目录未更新,homePath 在二级目录导致的404问题 const index = root.children?.findIndex( (item) => item.name === route.name, ); if (index !== undefined && index !== -1 && root.children) { root.children[index] = route; } } else { root.children?.push(route); } } else { router.addRoute(route); } }); if (root) { if (root.name) { router.removeRoute(root.name); } router.addRoute(root); } // 生成菜单 const accessibleMenus = generateMenus(accessibleRoutes, options.router); return { accessibleMenus, accessibleRoutes }; } /** * Generate routes * @param mode * @param options */ async function generateRoutes( mode: AccessModeType, options: GenerateMenuAndRoutesOptions, ) { const { forbiddenComponent, roles, routes } = options; let resultRoutes: RouteRecordRaw[] = routes; switch (mode) { case 'backend': { resultRoutes = await generateRoutesByBackend(options); break; } case 'frontend': { resultRoutes = await generateRoutesByFrontend( routes, roles || [], forbiddenComponent, ); break; } case 'mixed': { const [frontend_resultRoutes, backend_resultRoutes] = await Promise.all([ generateRoutesByFrontend(routes, roles || [], forbiddenComponent), generateRoutesByBackend(options), ]); resultRoutes = [...frontend_resultRoutes, ...backend_resultRoutes]; break; } } /** * 调整路由树,做以下处理: * 1. 对未添加redirect的路由添加redirect * 2. 将懒加载的组件名称修改为当前路由的名称(如果启用了keep-alive的话) */ resultRoutes = mapTree(resultRoutes, (route) => { // 重新包装component,使用与路由名称相同的name以支持keep-alive的条件缓存。 if ( route.meta?.keepAlive && isFunction(route.component) && route.name && isString(route.name) ) { const originalComponent = route.component as () => Promise<{ default: Component | DefineComponent; }>; route.component = async () => { const component = await originalComponent(); if (!component.default) return component; return defineComponent({ name: route.name as string, setup(props, { attrs, slots }) { return () => h(component.default, { ...props, ...attrs }, slots); }, }); }; } // 如果有redirect或者没有子路由,则直接返回 if (route.redirect || !route.children || route.children.length === 0) { return route; } const firstChild = route.children[0]; // 如果子路由不是以/开头,则直接返回,这种情况需要计算全部父级的path才能得出正确的path,这里不做处理 if (!firstChild?.path || !firstChild.path.startsWith('/')) { return route; } // 第一个路由如果有query参数 需要加上参数 const fistChildQuery = route.children[0]?.meta?.query; // 根目录菜单固定只有一个children 且path为/ 不需要添加redirect route.redirect = fistChildQuery && route.children.length !== 1 && route.path !== '/' ? setObjToUrlParams(firstChild.path, fistChildQuery) : firstChild.path; return route; }); return resultRoutes; } export { generateAccessible }; ================================================ FILE: packages/effects/access/src/directive.ts ================================================ /** * Global authority directive * Used for fine-grained control of component permissions * @Example v-access:role="[ROLE_NAME]" or v-access:role="ROLE_NAME" * @Example v-access:code="[ROLE_CODE]" or v-access:code="ROLE_CODE" */ import type { App, Directive, DirectiveBinding } from 'vue'; import { useAccess } from './use-access'; function isAccessible( el: Element, binding: DirectiveBinding, ) { const { hasAccessByCodes, hasAccessByRoles } = useAccess(); const value = binding.value; if (!value) return; const authMethod = binding.arg === 'role' ? hasAccessByRoles : hasAccessByCodes; const values = Array.isArray(value) ? value : [value]; if (!authMethod(values)) { el?.remove(); } } const mounted = (el: Element, binding: DirectiveBinding) => { isAccessible(el, binding); }; const authDirective: Directive = { mounted, }; export function registerAccessDirective(app: App) { app.directive('access', authDirective); } ================================================ FILE: packages/effects/access/src/index.ts ================================================ export { default as AccessControl } from './access-control.vue'; export * from './accessible'; export * from './directive'; export * from './use-access'; ================================================ FILE: packages/effects/access/src/use-access.ts ================================================ import { computed } from 'vue'; import { preferences, updatePreferences } from '@vben/preferences'; import { useAccessStore, useUserStore } from '@vben/stores'; function useAccess() { const accessStore = useAccessStore(); const userStore = useUserStore(); const accessMode = computed(() => { return preferences.app.accessMode; }); /** * 基于角色判断是否有权限 * @description: Determine whether there is permission,The role is judged by the user's role * @param roles */ function hasAccessByRoles(roles: string[]) { const userRoleSet = new Set(userStore.userRoles); // 超管的角色 if (userRoleSet.has('superadmin')) { return true; } const intersection = roles.filter((item) => userRoleSet.has(item)); return intersection.length > 0; } /** * 基于权限码判断是否有权限 * @description: Determine whether there is permission,The permission code is judged by the user's permission code * @param codes */ function hasAccessByCodes(codes: string[]) { const userCodesSet = new Set(accessStore.accessCodes); /** * 管理员权限 */ if (userCodesSet.has('*:*:*')) { return true; } // 其他 判断是否存在 const intersection = codes.filter((item) => userCodesSet.has(item)); return intersection.length > 0; } async function toggleAccessMode() { updatePreferences({ app: { accessMode: preferences.app.accessMode === 'frontend' ? 'backend' : 'frontend', }, }); } return { accessMode, hasAccessByCodes, hasAccessByRoles, toggleAccessMode, }; } export { useAccess }; ================================================ FILE: packages/effects/access/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/effects/common-ui/package.json ================================================ { "name": "@vben/common-ui", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/effects/common-ui" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" }, "./es/tippy": { "types": "./src/components/tippy/index.ts", "default": "./src/components/tippy/index.ts" }, "./es/loading": { "types": "./src/components/loading/index.ts", "default": "./src/components/loading/index.ts" } }, "dependencies": { "@codemirror/lang-html": "^6.4.9", "@codemirror/lang-java": "^6.0.1", "@codemirror/lang-javascript": "^6.2.2", "@codemirror/lang-sql": "^6.7.1", "@codemirror/lang-vue": "^0.1.3", "@codemirror/lang-xml": "^6.1.0", "@codemirror/theme-one-dark": "^6.1.2", "@vben-core/form-ui": "workspace:*", "@vben-core/popup-ui": "workspace:*", "@vben-core/preferences": "workspace:*", "@vben-core/shadcn-ui": "workspace:*", "@vben-core/shared": "workspace:*", "@vben/constants": "workspace:*", "@vben/hooks": "workspace:*", "@vben/icons": "workspace:*", "@vben/locales": "workspace:*", "@vben/preferences": "workspace:*", "@vben/types": "workspace:*", "@vueuse/core": "catalog:", "@vueuse/integrations": "catalog:", "codemirror": "6.0.1", "json-bigint": "catalog:", "qrcode": "catalog:", "tippy.js": "catalog:", "vditor": "3.10.9", "vue": "catalog:", "vue-codemirror6": "1.3.4", "vue-json-pretty": "^2.4.0", "vue-json-viewer": "catalog:", "vue-router": "catalog:", "vue-tippy": "catalog:" }, "devDependencies": { "@types/qrcode": "catalog:" } } ================================================ FILE: packages/effects/common-ui/src/components/api-component/api-component.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/api-component/index.ts ================================================ export { default as ApiComponent } from './api-component.vue'; ================================================ FILE: packages/effects/common-ui/src/components/captcha/hooks/useCaptchaPoints.ts ================================================ import type { CaptchaPoint } from '../types'; import { reactive } from 'vue'; export function useCaptchaPoints() { const points = reactive([]); function addPoint(point: CaptchaPoint) { points.push(point); } function clearPoints() { points.splice(0); } return { addPoint, clearPoints, points, }; } ================================================ FILE: packages/effects/common-ui/src/components/captcha/index.ts ================================================ export { default as PointSelectionCaptcha } from './point-selection-captcha/index.vue'; export { default as PointSelectionCaptchaCard } from './point-selection-captcha/index.vue'; export { default as SliderCaptcha } from './slider-captcha/index.vue'; export { default as SliderRotateCaptcha } from './slider-rotate-captcha/index.vue'; export { default as SliderTranslateCaptcha } from './slider-translate-captcha/index.vue'; export type * from './types'; ================================================ FILE: packages/effects/common-ui/src/components/captcha/point-selection-captcha/index.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/point-selection-captcha/point-selection-captcha-card.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/slider-captcha/index.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/slider-captcha/slider-captcha-action.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/slider-captcha/slider-captcha-bar.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/slider-captcha/slider-captcha-content.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/slider-rotate-captcha/index.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/slider-translate-captcha/index.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/captcha/types.ts ================================================ import type { ClassType } from '@vben/types'; import type { CSSProperties } from 'vue'; export interface CaptchaData { /** * x */ x: number; /** * y */ y: number; /** * 时间戳 */ t: number; } export interface CaptchaPoint extends CaptchaData { /** * 数据索引 */ i: number; } export interface PointSelectionCaptchaCardProps { /** * 验证码图片 */ captchaImage: string; /** * 验证码图片高度 * @default '220px' */ height?: number | string; /** * 水平内边距 * @default '12px' */ paddingX?: number | string; /** * 垂直内边距 * @default '16px' */ paddingY?: number | string; /** * 标题 * @default '请按图依次点击' */ title?: string; /** * 验证码图片宽度 * @default '300px' */ width?: number | string; } export interface PointSelectionCaptchaProps extends PointSelectionCaptchaCardProps { /** * 是否展示确定按钮 * @default false */ showConfirm?: boolean; /** * 提示图片 * @default '' */ hintImage?: string; /** * 提示文本 * @default '' */ hintText?: string; } export interface SliderCaptchaProps { class?: ClassType; /** * @description 滑块的样式 * @default {} */ actionStyle?: CSSProperties; /** * @description 滑块条的样式 * @default {} */ barStyle?: CSSProperties; /** * @description 内容的样式 * @default {} */ contentStyle?: CSSProperties; /** * @description 组件的样式 * @default {} */ wrapperStyle?: CSSProperties; /** * @description 是否作为插槽使用,用于联动组件,可参考旋转校验组件 * @default false */ isSlot?: boolean; /** * @description 验证成功的提示 * @default '验证通过' */ successText?: string; /** * @description 提示文字 * @default '请按住滑块拖动' */ text?: string; } export interface SliderRotateCaptchaProps { /** * @description 旋转的角度 * @default 20 */ diffDegree?: number; /** * @description 图片的宽度 * @default 260 */ imageSize?: number; /** * @description 图片的样式 * @default {} */ imageWrapperStyle?: CSSProperties; /** * @description 最大旋转角度 * @default 270 */ maxDegree?: number; /** * @description 最小旋转角度 * @default 90 */ minDegree?: number; /** * @description 图片的地址 */ src?: string; /** * @description 默认提示文本 */ defaultTip?: string; } export interface SliderTranslateCaptchaProps { /** * @description 拼图的宽度 * @default 420 */ canvasWidth?: number; /** * @description 拼图的高度 * @default 280 */ canvasHeight?: number; /** * @description 切块上正方形的长度 * @default 42 */ squareLength?: number; /** * @description 切块上圆形的半径 * @default 10 */ circleRadius?: number; /** * @description 图片的地址 */ src?: string; /** * @description 允许的最大差距 * @default 3 */ diffDistance?: number; /** * @description 默认提示文本 */ defaultTip?: string; } export interface CaptchaVerifyPassingData { isPassing: boolean; time: number | string; } export interface SliderCaptchaActionType { resume: () => void; } export interface SliderRotateVerifyPassingData { event: MouseEvent | TouchEvent; moveDistance: number; moveX: number; } ================================================ FILE: packages/effects/common-ui/src/components/code-mirror/code-mirror.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/code-mirror/data.ts ================================================ import { html } from '@codemirror/lang-html'; import { java } from '@codemirror/lang-java'; import { javascript } from '@codemirror/lang-javascript'; import { sql } from '@codemirror/lang-sql'; import { vue } from '@codemirror/lang-vue'; import { xml } from '@codemirror/lang-xml'; /** * 可自行安装依赖并按格式配置 函数形参为配置项 * @see https://github.com/logue/vue-codemirror6?tab=readme-ov-file#supported-languages Language Support项 */ export const languageSupportMap = { html: html(), java: java(), js: javascript(), jsx: javascript({ jsx: true }), sql: sql(), ts: javascript({ typescript: true }), tsx: javascript({ jsx: true, typescript: true }), vue: vue(), xml: xml(), }; export type LanguageSupport = keyof typeof languageSupportMap; ================================================ FILE: packages/effects/common-ui/src/components/code-mirror/index.ts ================================================ export { default as CodeMirror } from './code-mirror.vue'; export type { LanguageSupport } from './data'; ================================================ FILE: packages/effects/common-ui/src/components/col-page/col-page.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/col-page/index.ts ================================================ export { default as ColPage } from './col-page.vue'; export * from './types'; ================================================ FILE: packages/effects/common-ui/src/components/col-page/types.ts ================================================ import type { PageProps } from '../page/types'; export interface ColPageProps extends PageProps { /** * 左侧宽度 * @default 30 */ leftWidth?: number; leftMinWidth?: number; leftMaxWidth?: number; leftCollapsedWidth?: number; leftCollapsible?: boolean; /** * 右侧宽度 * @default 70 */ rightWidth?: number; rightMinWidth?: number; rightCollapsedWidth?: number; rightMaxWidth?: number; rightCollapsible?: boolean; resizable?: boolean; splitLine?: boolean; splitHandle?: boolean; } ================================================ FILE: packages/effects/common-ui/src/components/count-to/count-to.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/count-to/index.ts ================================================ export { default as CountTo } from './count-to.vue'; export * from './types'; ================================================ FILE: packages/effects/common-ui/src/components/count-to/types.ts ================================================ import type { CubicBezierPoints, EasingFunction } from '@vueuse/core'; import type { StyleValue } from 'vue'; import { TransitionPresets as TransitionPresetsData } from '@vueuse/core'; export type TransitionPresets = keyof typeof TransitionPresetsData; export const TransitionPresetsKeys = Object.keys( TransitionPresetsData, ) as TransitionPresets[]; export interface CountToProps { /** 初始值 */ startVal?: number; /** 当前值 */ endVal: number; /** 是否禁用动画 */ disabled?: boolean; /** 延迟动画开始的时间 */ delay?: number; /** 持续时间 */ duration?: number; /** 小数位数 */ decimals?: number; /** 小数点 */ decimal?: string; /** 分隔符 */ separator?: string; /** 前缀 */ prefix?: string; /** 后缀 */ suffix?: string; /** 过渡效果 */ transition?: CubicBezierPoints | EasingFunction | TransitionPresets; /** 整数部分的类名 */ mainClass?: string; /** 小数部分的类名 */ decimalClass?: string; /** 前缀部分的类名 */ prefixClass?: string; /** 后缀部分的类名 */ suffixClass?: string; /** 整数部分的样式 */ mainStyle?: StyleValue; /** 小数部分的样式 */ decimalStyle?: StyleValue; /** 前缀部分的样式 */ prefixStyle?: StyleValue; /** 后缀部分的样式 */ suffixStyle?: StyleValue; } ================================================ FILE: packages/effects/common-ui/src/components/ellipsis-text/ellipsis-text.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/ellipsis-text/index.ts ================================================ export { default as EllipsisText } from './ellipsis-text.vue'; ================================================ FILE: packages/effects/common-ui/src/components/icon-picker/icon-picker.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/icon-picker/icons.ts ================================================ import type { Recordable } from '@vben/types'; /** * 一个缓存对象,在不刷新页面时,无需重复请求远程接口 */ export const ICONS_MAP: Recordable = {}; interface IconifyResponse { prefix: string; total: number; title: string; uncategorized?: string[]; categories?: Recordable; aliases?: Recordable; } const PENDING_REQUESTS: Recordable> = {}; /** * 通过Iconify接口获取图标集数据。 * 同一时间多个图标选择器同时请求同一个图标集时,实际上只会发起一次请求(所有请求共享同一份结果)。 * 请求结果会被缓存,刷新页面前同一个图标集不会再次请求 * @param prefix 图标集名称 * @returns 图标集中包含的所有图标名称 */ export async function fetchIconsData(prefix: string): Promise { if (Reflect.has(ICONS_MAP, prefix) && ICONS_MAP[prefix]) { return ICONS_MAP[prefix]; } if (Reflect.has(PENDING_REQUESTS, prefix) && PENDING_REQUESTS[prefix]) { return PENDING_REQUESTS[prefix]; } PENDING_REQUESTS[prefix] = (async () => { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 1000 * 10); const response: IconifyResponse = await fetch( `https://api.iconify.design/collection?prefix=${prefix}`, { signal: controller.signal }, ).then((res) => res.json()); clearTimeout(timeoutId); const list = response.uncategorized || []; if (response.categories) { for (const category in response.categories) { list.push(...(response.categories[category] || [])); } } ICONS_MAP[prefix] = list.map((v) => `${prefix}:${v}`); } catch (error) { console.error(`Failed to fetch icons for prefix ${prefix}:`, error); return [] as string[]; } return ICONS_MAP[prefix]; })(); return PENDING_REQUESTS[prefix]; } ================================================ FILE: packages/effects/common-ui/src/components/icon-picker/index.ts ================================================ export { default as IconPicker } from './icon-picker.vue'; ================================================ FILE: packages/effects/common-ui/src/components/index.ts ================================================ export * from './api-component'; export * from './captcha'; export * from './code-mirror'; export * from './col-page'; export * from './count-to'; export * from './ellipsis-text'; export * from './icon-picker'; export * from './json-preview'; export * from './json-viewer'; export * from './loading'; export * from './markdown'; export * from './page'; export * from './resize'; export * from './tippy'; export * from './tree'; export * from '@vben-core/form-ui'; export * from '@vben-core/popup-ui'; // 给文档用 export { VbenAvatar, VbenButton, VbenButtonGroup, VbenCheckbox, VbenCheckButtonGroup, VbenCountToAnimator, VbenFullScreen, VbenInputPassword, VbenLoading, VbenLogo, VbenPinInput, VbenSelect, VbenSpinner, } from '@vben-core/shadcn-ui'; export type { FlattenedItem } from '@vben-core/shadcn-ui'; export { globalShareState } from '@vben-core/shared/global-state'; ================================================ FILE: packages/effects/common-ui/src/components/json-preview/index.ts ================================================ export { default as JsonPreview } from './json-preview.vue'; ================================================ FILE: packages/effects/common-ui/src/components/json-preview/json-preview.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/json-viewer/index.ts ================================================ export { default as JsonViewer } from './index.vue'; export * from './types'; ================================================ FILE: packages/effects/common-ui/src/components/json-viewer/index.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/json-viewer/style.scss ================================================ .default-json-theme { font-family: Consolas, Menlo, Courier, monospace; font-size: 14px; color: hsl(var(--foreground)); white-space: nowrap; background: hsl(var(--background)); &.jv-container.boxed { border: 1px solid hsl(var(--border)); } .jv-ellipsis { display: inline-block; padding: 0 4px 2px; font-size: 0.9em; line-height: 0.9; color: hsl(var(--secondary-foreground)); vertical-align: 2px; cursor: pointer; user-select: none; background-color: hsl(var(--secondary)); border-radius: 3px; } .jv-button { color: hsl(var(--primary)); } .jv-key { color: hsl(var(--heavy-foreground)); } .jv-item { &.jv-array { color: hsl(var(--heavy-foreground)); } &.jv-boolean { color: hsl(var(--red-400)); } &.jv-function { color: hsl(var(--destructive-foreground)); } &.jv-number { color: hsl(var(--info-foreground)); } &.jv-number-float { color: hsl(var(--info-foreground)); } &.jv-number-integer { color: hsl(var(--info-foreground)); } &.jv-object { color: hsl(var(--accent-darker)); } &.jv-undefined { color: hsl(var(--secondary-foreground)); } &.jv-string { color: hsl(var(--primary)); overflow-wrap: break-word; white-space: normal; } } &.jv-container .jv-code { padding: 10px; &.boxed:not(.open) { padding-bottom: 20px; margin-bottom: 10px; } &.open { padding-bottom: 10px; } .jv-toggle { &::before { padding: 0 2px; border-radius: 2px; } &:hover { &::before { background: hsl(var(--accent-foreground)); } } } } } ================================================ FILE: packages/effects/common-ui/src/components/json-viewer/types.ts ================================================ export interface JsonViewerProps { /** 要展示的结构数据 */ value: any; /** 展开深度 */ expandDepth?: number; /** 是否可复制 */ copyable?: boolean; /** 是否排序 */ sort?: boolean; /** 显示边框 */ boxed?: boolean; /** 主题 */ theme?: string; /** 是否展开 */ expanded?: boolean; /** 时间格式化函数 */ timeformat?: (time: Date | number | string) => string; /** 预览模式 */ previewMode?: boolean; /** 显示数组索引 */ showArrayIndex?: boolean; /** 显示双引号 */ showDoubleQuotes?: boolean; } export interface JsonViewerAction { action: string; text: string; trigger: HTMLElement; } export interface JsonViewerValue { value: any; path: string; depth: number; el: HTMLElement; } export interface JsonViewerToggle { /** 鼠标事件 */ event: MouseEvent; /** 当前展开状态 */ open: boolean; } ================================================ FILE: packages/effects/common-ui/src/components/loading/directive.ts ================================================ import type { App, Directive, DirectiveBinding } from 'vue'; import { h, render } from 'vue'; import { VbenLoading, VbenSpinner } from '@vben-core/shadcn-ui'; import { isString } from '@vben-core/shared/utils'; const LOADING_INSTANCE_KEY = Symbol('loading'); const SPINNER_INSTANCE_KEY = Symbol('spinner'); const CLASS_NAME_RELATIVE = 'spinner-parent--relative'; const loadingDirective: Directive = { mounted(el, binding) { const instance = h(VbenLoading, getOptions(binding)); render(instance, el); el.classList.add(CLASS_NAME_RELATIVE); el[LOADING_INSTANCE_KEY] = instance; }, unmounted(el) { const instance = el[LOADING_INSTANCE_KEY]; el.classList.remove(CLASS_NAME_RELATIVE); render(null, el); instance.el.remove(); el[LOADING_INSTANCE_KEY] = null; }, updated(el, binding) { const instance = el[LOADING_INSTANCE_KEY]; const options = getOptions(binding); if (options && instance?.component) { try { Object.keys(options).forEach((key) => { instance.component.props[key] = options[key]; }); instance.component.update(); } catch (error) { console.error( 'Failed to update loading component in directive:', error, ); } } }, }; function getOptions(binding: DirectiveBinding) { if (binding.value === undefined) { return { spinning: true }; } else if (typeof binding.value === 'boolean') { return { spinning: binding.value }; } else { return { ...binding.value }; } } const spinningDirective: Directive = { mounted(el, binding) { const instance = h(VbenSpinner, getOptions(binding)); render(instance, el); el.classList.add(CLASS_NAME_RELATIVE); el[SPINNER_INSTANCE_KEY] = instance; }, unmounted(el) { const instance = el[SPINNER_INSTANCE_KEY]; el.classList.remove(CLASS_NAME_RELATIVE); render(null, el); instance.el.remove(); el[SPINNER_INSTANCE_KEY] = null; }, updated(el, binding) { const instance = el[SPINNER_INSTANCE_KEY]; const options = getOptions(binding); if (options && instance?.component) { try { Object.keys(options).forEach((key) => { instance.component.props[key] = options[key]; }); instance.component.update(); } catch (error) { console.error( 'Failed to update spinner component in directive:', error, ); } } }, }; type loadingDirectiveParams = { /** 是否注册loading指令。如果提供一个string,则将指令注册为指定的名称 */ loading?: boolean | string; /** 是否注册spinning指令。如果提供一个string,则将指令注册为指定的名称 */ spinning?: boolean | string; }; /** * 注册loading指令 * @param app * @param params */ export function registerLoadingDirective( app: App, params?: loadingDirectiveParams, ) { // 注入一个样式供指令使用,确保容器是相对定位 const style = document.createElement('style'); style.id = CLASS_NAME_RELATIVE; style.innerHTML = ` .${CLASS_NAME_RELATIVE} { position: relative !important; } `; document.head.append(style); if (params?.loading !== false) { app.directive( isString(params?.loading) ? params.loading : 'loading', loadingDirective, ); } if (params?.spinning !== false) { app.directive( isString(params?.spinning) ? params.spinning : 'spinning', spinningDirective, ); } } ================================================ FILE: packages/effects/common-ui/src/components/loading/index.ts ================================================ export * from './directive'; export { default as Loading } from './loading.vue'; export { default as Spinner } from './spinner.vue'; ================================================ FILE: packages/effects/common-ui/src/components/loading/loading.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/loading/spinner.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/markdown/editor.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/markdown/index.ts ================================================ export { default as MarkdownEditor } from './editor.vue'; export { default as MarkdownPreviewer } from './preview.vue'; ================================================ FILE: packages/effects/common-ui/src/components/markdown/preview.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/page/__tests__/page.test.ts ================================================ import { mount } from '@vue/test-utils'; import { describe, expect, it } from 'vitest'; import { Page } from '..'; describe('page.vue', () => { it('renders title when passed', () => { const wrapper = mount(Page, { props: { title: 'Test Title', }, }); expect(wrapper.text()).toContain('Test Title'); }); it('renders description when passed', () => { const wrapper = mount(Page, { props: { description: 'Test Description', }, }); expect(wrapper.text()).toContain('Test Description'); }); it('renders default slot content', () => { const wrapper = mount(Page, { slots: { default: '

Default Slot Content

', }, }); expect(wrapper.html()).toContain('

Default Slot Content

'); }); it('renders footer slot when showFooter is true', () => { const wrapper = mount(Page, { props: { showFooter: true, }, slots: { footer: '

Footer Slot Content

', }, }); expect(wrapper.html()).toContain('

Footer Slot Content

'); }); it('applies the custom contentClass', () => { const wrapper = mount(Page, { props: { contentClass: 'custom-class', }, }); const contentDiv = wrapper.find('.p-4'); expect(contentDiv.classes()).toContain('custom-class'); }); it('does not render title slot if title prop is provided', () => { const wrapper = mount(Page, { props: { title: 'Test Title', }, slots: { title: '

Title Slot Content

', }, }); expect(wrapper.text()).toContain('Title Slot Content'); expect(wrapper.html()).not.toContain('Test Title'); }); it('does not render description slot if description prop is provided', () => { const wrapper = mount(Page, { props: { description: 'Test Description', }, slots: { description: '

Description Slot Content

', }, }); expect(wrapper.text()).toContain('Description Slot Content'); expect(wrapper.html()).not.toContain('Test Description'); }); }); ================================================ FILE: packages/effects/common-ui/src/components/page/index.ts ================================================ export { default as Page } from './page.vue'; export * from './types'; ================================================ FILE: packages/effects/common-ui/src/components/page/page.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/page/types.ts ================================================ export interface PageProps { title?: string; description?: string; contentClass?: string; /** * 根据content可见高度自适应 */ autoContentHeight?: boolean; headerClass?: string; footerClass?: string; /** * Custom height offset value (in pixels) to adjust content area sizing * when used with autoContentHeight * @default 0 */ heightOffset?: number; } ================================================ FILE: packages/effects/common-ui/src/components/resize/index.ts ================================================ export { default as VResize } from './resize.vue'; ================================================ FILE: packages/effects/common-ui/src/components/resize/resize.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/components/tippy/directive.ts ================================================ import type { ComputedRef, Directive } from 'vue'; import { useTippy } from 'vue-tippy'; export default function useTippyDirective(isDark: ComputedRef) { const directive: Directive = { mounted(el, binding, vnode) { const opts = typeof binding.value === 'string' ? { content: binding.value } : binding.value || {}; const modifiers = Object.keys(binding.modifiers || {}); const placement = modifiers.find((modifier) => modifier !== 'arrow'); const withArrow = modifiers.includes('arrow'); if (placement) { opts.placement = opts.placement || placement; } if (withArrow) { opts.arrow = opts.arrow === undefined ? true : opts.arrow; } if (vnode.props && vnode.props.onTippyShow) { opts.onShow = function (...args: any[]) { return vnode.props?.onTippyShow(...args); }; } if (vnode.props && vnode.props.onTippyShown) { opts.onShown = function (...args: any[]) { return vnode.props?.onTippyShown(...args); }; } if (vnode.props && vnode.props.onTippyHidden) { opts.onHidden = function (...args: any[]) { return vnode.props?.onTippyHidden(...args); }; } if (vnode.props && vnode.props.onTippyHide) { opts.onHide = function (...args: any[]) { return vnode.props?.onTippyHide(...args); }; } if (vnode.props && vnode.props.onTippyMount) { opts.onMount = function (...args: any[]) { return vnode.props?.onTippyMount(...args); }; } if (el.getAttribute('title') && !opts.content) { opts.content = el.getAttribute('title'); el.removeAttribute('title'); } if (el.getAttribute('content') && !opts.content) { opts.content = el.getAttribute('content'); } useTippy(el, opts); }, unmounted(el) { if (el.$tippy) { el.$tippy.destroy(); } else if (el._tippy) { el._tippy.destroy(); } }, updated(el, binding) { const opts = typeof binding.value === 'string' ? { content: binding.value, theme: isDark.value ? '' : 'light' } : Object.assign( { theme: isDark.value ? '' : 'light' }, binding.value, ); if (el.getAttribute('title') && !opts.content) { opts.content = el.getAttribute('title'); el.removeAttribute('title'); } if (el.getAttribute('content') && !opts.content) { opts.content = el.getAttribute('content'); } if (el.$tippy) { el.$tippy.setProps(opts || {}); } else if (el._tippy) { el._tippy.setProps(opts || {}); } }, }; return directive; } ================================================ FILE: packages/effects/common-ui/src/components/tippy/index.ts ================================================ import type { DefaultProps, Props } from 'tippy.js'; import type { App, SetupContext } from 'vue'; import { h, watchEffect } from 'vue'; import { setDefaultProps, Tippy as TippyComponent } from 'vue-tippy'; import { usePreferences } from '@vben-core/preferences'; import useTippyDirective from './directive'; import 'tippy.js/dist/tippy.css'; import 'tippy.js/dist/backdrop.css'; import 'tippy.js/themes/light.css'; import 'tippy.js/animations/scale.css'; import 'tippy.js/animations/shift-toward.css'; import 'tippy.js/animations/shift-away.css'; import 'tippy.js/animations/perspective.css'; const { isDark } = usePreferences(); export type TippyProps = Partial< Props & { animation?: | 'fade' | 'perspective' | 'scale' | 'shift-away' | 'shift-toward' | boolean; theme?: 'auto' | 'dark' | 'light'; } >; export function initTippy(app: App, options?: DefaultProps) { setDefaultProps({ allowHTML: true, delay: [500, 200], theme: isDark.value ? '' : 'light', ...options, }); if (!options || !Reflect.has(options, 'theme') || options.theme === 'auto') { watchEffect(() => { setDefaultProps({ theme: isDark.value ? '' : 'light' }); }); } app.directive('tippy', useTippyDirective(isDark)); } export const Tippy = (props: any, { attrs, slots }: SetupContext) => { let theme: string = (attrs.theme as string) ?? 'auto'; if (theme === 'auto') { theme = isDark.value ? '' : 'light'; } if (theme === 'dark') { theme = ''; } return h( TippyComponent, { ...props, ...attrs, theme, }, slots, ); }; ================================================ FILE: packages/effects/common-ui/src/components/tree/index.ts ================================================ export { default as Tree } from './tree.vue'; ================================================ FILE: packages/effects/common-ui/src/components/tree/tree.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/index.ts ================================================ export * from './components'; export * from './ui'; export { VbenAvatar } from '@vben-core/shadcn-ui'; ================================================ FILE: packages/effects/common-ui/src/ui/about/about.ts ================================================ import type { Component } from 'vue'; interface AboutProps { description?: string; name?: string; title?: string; } interface DescriptionItem { content: Component | string; title: string; } export type { AboutProps, DescriptionItem }; ================================================ FILE: packages/effects/common-ui/src/ui/about/about.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/about/index.ts ================================================ export { default as About } from './about.vue'; ================================================ FILE: packages/effects/common-ui/src/ui/authentication/auth-title.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/code-login.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/dingding-login.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/forget-password.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/index.ts ================================================ export { default as AuthenticationCodeLogin } from './code-login.vue'; export { default as AuthenticationForgetPassword } from './forget-password.vue'; export { default as AuthenticationLogin } from './login.vue'; export { default as AuthenticationLoginExpiredModal } from './login-expired-modal.vue'; export { default as AuthenticationQrCodeLogin } from './qrcode-login.vue'; export { default as AuthenticationRegister } from './register.vue'; export type { AuthenticationProps, GrantType, LoginAndRegisterParams, LoginCodeParams, } from './types'; ================================================ FILE: packages/effects/common-ui/src/ui/authentication/login-expired-modal.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/login.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/qrcode-login.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/register.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/third-party-login.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/authentication/types.ts ================================================ interface AuthenticationProps { /** * @zh_CN 验证码登录路径 */ codeLoginPath?: string; /** * @zh_CN 忘记密码路径 */ forgetPasswordPath?: string; /** * @zh_CN 是否处于加载处理状态 */ loading?: boolean; /** * @zh_CN 二维码登录路径 */ qrCodeLoginPath?: string; /** * @zh_CN 注册路径 */ registerPath?: string; /** * @zh_CN 是否显示验证码登录 */ showCodeLogin?: boolean; /** * @zh_CN 是否显示忘记密码 */ showForgetPassword?: boolean; /** * @zh_CN 是否显示二维码登录 */ showQrcodeLogin?: boolean; /** * @zh_CN 是否显示注册按钮 */ showRegister?: boolean; /** * @zh_CN 是否显示记住账号 */ showRememberMe?: boolean; /** * @zh_CN 是否显示第三方登录 */ showThirdPartyLogin?: boolean; /** * @zh_CN 登录框子标题 */ subTitle?: string; /** * @zh_CN 登录框标题 */ title?: string; /** * @zh_CN 提交按钮文本 */ submitButtonText?: string; } /** * 登录类型 * password 密码 * sms 短信 * social 第三方oauth * email 邮箱 * xcx 小程序 */ type GrantType = 'email' | 'password' | 'sms' | 'social' | 'xcx'; interface LoginAndRegisterParams { code?: string; grantType: GrantType; password: string; tenantId: string; username: string; uuid?: string; } interface LoginCodeParams { tenantId: string; code: string; phoneNumber: string; } interface LoginEmits { submit: [LoginAndRegisterParams]; } interface LoginCodeEmits { submit: [LoginCodeParams]; } interface RegisterEmits { submit: [LoginAndRegisterParams]; } export type { AuthenticationProps, GrantType, LoginAndRegisterParams, LoginCodeEmits, LoginCodeParams, LoginEmits, RegisterEmits, }; ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/analysis/analysis-chart-card.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/analysis/analysis-charts-tabs.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/analysis/analysis-overview.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/analysis/index.ts ================================================ export { default as AnalysisChartCard } from './analysis-chart-card.vue'; export { default as AnalysisChartsTabs } from './analysis-charts-tabs.vue'; export { default as AnalysisOverview } from './analysis-overview.vue'; ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/index.ts ================================================ export * from './analysis'; export type * from './typing'; export * from './workbench'; ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/typing.ts ================================================ import type { Component } from 'vue'; interface AnalysisOverviewItem { icon: Component | string; title: string; totalTitle: string; totalValue: number; value: number; } interface WorkbenchProjectItem { color?: string; content: string; date: string; group: string; icon: Component | string; title: string; url?: string; } interface WorkbenchTrendItem { avatar: string; content: string; date: string; title: string; } interface WorkbenchTodoItem { completed: boolean; content: string; date: string; title: string; } interface WorkbenchQuickNavItem { color?: string; icon: Component | string; title: string; url?: string; } export type { AnalysisOverviewItem, WorkbenchProjectItem, WorkbenchQuickNavItem, WorkbenchTodoItem, WorkbenchTrendItem, }; ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/workbench/index.ts ================================================ export { default as WorkbenchHeader } from './workbench-header.vue'; export { default as WorkbenchProject } from './workbench-project.vue'; export { default as WorkbenchQuickNav } from './workbench-quick-nav.vue'; export { default as WorkbenchTodo } from './workbench-todo.vue'; export { default as WorkbenchTrends } from './workbench-trends.vue'; ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/workbench/workbench-header.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/workbench/workbench-project.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/workbench/workbench-quick-nav.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/workbench/workbench-todo.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/dashboard/workbench/workbench-trends.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/fallback/fallback.ts ================================================ interface FallbackProps { /** * 描述 */ description?: string; /** * @zh_CN 首页路由地址 * @default / */ homePath?: string; /** * @zh_CN 默认显示的图片 * @default pageNotFoundSvg */ image?: string; /** * @zh_CN 内置类型 */ status?: '403' | '404' | '500' | 'coming-soon' | 'offline'; /** * @zh_CN 页面提示语 */ title?: string; } export type { FallbackProps }; ================================================ FILE: packages/effects/common-ui/src/ui/fallback/fallback.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/fallback/icons/icon-403.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/fallback/icons/icon-404.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/fallback/icons/icon-500.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/fallback/icons/icon-coming-soon.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/fallback/icons/icon-offline.vue ================================================ ================================================ FILE: packages/effects/common-ui/src/ui/fallback/index.ts ================================================ export type * from './fallback'; export { default as Fallback } from './fallback.vue'; ================================================ FILE: packages/effects/common-ui/src/ui/index.ts ================================================ export * from './about'; export * from './authentication'; export * from './dashboard'; export * from './fallback'; ================================================ FILE: packages/effects/common-ui/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/effects/hooks/README.md ================================================ # @vben/hooks 用于多个 `app` 公用的 hook,继承了 `@vben/hooks` 的所有能力。业务上有通用 hooks 可以放在这里。 ## 用法 ### 添加依赖 ```bash # 进入目标应用目录,例如 apps/xxxx-app # cd apps/xxxx-app pnpm add @vben/hooks ``` ### 使用 ```ts import { useNamespace } from '@vben/hooks'; ``` ================================================ FILE: packages/effects/hooks/package.json ================================================ { "name": "@vben/hooks", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/effects/hooks" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben-core/composables": "workspace:*", "@vben/preferences": "workspace:*", "@vben/stores": "workspace:*", "@vben/types": "workspace:*", "@vben/utils": "workspace:*", "@vueuse/core": "catalog:", "vue": "catalog:", "vue-router": "catalog:", "watermark-js-plus": "catalog:" } } ================================================ FILE: packages/effects/hooks/src/index.ts ================================================ export * from './use-app-config'; export * from './use-content-maximize'; export * from './use-design-tokens'; export * from './use-hover-toggle'; export * from './use-pagination'; export * from './use-refresh'; export * from './use-tabs'; export * from './use-watermark'; export * from '@vben-core/composables'; ================================================ FILE: packages/effects/hooks/src/use-app-config.ts ================================================ import type { ApplicationConfig, VbenAdminProAppConfigRaw, } from '@vben/types/global'; /** * 由 vite-inject-app-config 注入的全局配置 */ export function useAppConfig( env: Record, isProduction: boolean, ): ApplicationConfig { // 生产环境下,直接使用 window._VBEN_ADMIN_PRO_APP_CONF_ 全局变量 const config = isProduction ? window._VBEN_ADMIN_PRO_APP_CONF_ : (env as VbenAdminProAppConfigRaw); const { VITE_GLOB_API_URL, VITE_GLOB_APP_CLIENT_ID, VITE_GLOB_ENABLE_ENCRYPT, VITE_GLOB_RSA_PRIVATE_KEY, VITE_GLOB_RSA_PUBLIC_KEY, VITE_GLOB_SSE_ENABLE, VITE_GLOB_WEBSOCKET_ENABLE, } = config; return { // 后端地址 apiURL: VITE_GLOB_API_URL, // 客户端key clientId: VITE_GLOB_APP_CLIENT_ID, enableEncrypt: VITE_GLOB_ENABLE_ENCRYPT === 'true', // RSA私钥 rsaPrivateKey: VITE_GLOB_RSA_PRIVATE_KEY, // RSA公钥 rsaPublicKey: VITE_GLOB_RSA_PUBLIC_KEY, // 是否开启sse sseEnable: VITE_GLOB_SSE_ENABLE === 'true', // 是否开启websocket websocketEnable: VITE_GLOB_WEBSOCKET_ENABLE === 'true', }; } ================================================ FILE: packages/effects/hooks/src/use-content-maximize.ts ================================================ import { updatePreferences, usePreferences } from '@vben/preferences'; /** * 主体区域最大化 */ export function useContentMaximize() { const { contentIsMaximize } = usePreferences(); function toggleMaximize() { const isMaximize = contentIsMaximize.value; updatePreferences({ header: { hidden: !isMaximize, }, sidebar: { hidden: !isMaximize, }, }); } return { contentIsMaximize, toggleMaximize, }; } ================================================ FILE: packages/effects/hooks/src/use-design-tokens.ts ================================================ import { reactive, watch } from 'vue'; import { preferences, usePreferences } from '@vben/preferences'; import { convertToRgb, updateCSSVariables } from '@vben/utils'; /** * 用于适配各个框架的设计系统 */ export function useAntdDesignTokens() { const rootStyles = getComputedStyle(document.documentElement); const tokens = reactive({ borderRadius: '' as any, colorBgBase: '', colorBgContainer: '', colorBgElevated: '', colorBgLayout: '', colorBgMask: '', colorBorder: '', colorBorderSecondary: '', colorError: '', colorInfo: '', colorPrimary: '', colorSuccess: '', colorTextBase: '', colorWarning: '', zIndexPopupBase: 2000, // 调整基础弹层层级,避免下拉等组件被弹窗或者最大化状态下的表格遮挡 }); const getCssVariableValue = (variable: string, isColor: boolean = true) => { const value = rootStyles.getPropertyValue(variable); return isColor ? `hsl(${value})` : value; }; watch( () => preferences.theme, () => { tokens.colorPrimary = getCssVariableValue('--primary'); tokens.colorInfo = getCssVariableValue('--primary'); tokens.colorError = getCssVariableValue('--destructive'); tokens.colorWarning = getCssVariableValue('--warning'); tokens.colorSuccess = getCssVariableValue('--success'); tokens.colorTextBase = getCssVariableValue('--foreground'); getCssVariableValue('--primary-foreground'); tokens.colorBorderSecondary = tokens.colorBorder = getCssVariableValue('--border'); tokens.colorBgElevated = getCssVariableValue('--popover'); tokens.colorBgContainer = getCssVariableValue('--card'); tokens.colorBgBase = getCssVariableValue('--background'); const radius = Number.parseFloat(getCssVariableValue('--radius', false)); // 1rem = 16px tokens.borderRadius = radius * 16; tokens.colorBgLayout = getCssVariableValue('--background-deep'); tokens.colorBgMask = getCssVariableValue('--overlay'); }, { immediate: true }, ); return { tokens, }; } export function useNaiveDesignTokens() { const rootStyles = getComputedStyle(document.documentElement); const commonTokens = reactive({ baseColor: '', bodyColor: '', borderColor: '', borderRadius: '', cardColor: '', dividerColor: '', errorColor: '', errorColorHover: '', errorColorPressed: '', errorColorSuppl: '', invertedColor: '', modalColor: '', popoverColor: '', primaryColor: '', primaryColorHover: '', primaryColorPressed: '', primaryColorSuppl: '', successColor: '', successColorHover: '', successColorPressed: '', successColorSuppl: '', tableColor: '', textColorBase: '', warningColor: '', warningColorHover: '', warningColorPressed: '', warningColorSuppl: '', }); const getCssVariableValue = (variable: string, isColor: boolean = true) => { const value = rootStyles.getPropertyValue(variable); return isColor ? convertToRgb(`hsl(${value})`) : value; }; watch( () => preferences.theme, () => { commonTokens.primaryColor = getCssVariableValue('--primary'); commonTokens.primaryColorHover = getCssVariableValue('--primary-600'); commonTokens.primaryColorPressed = getCssVariableValue('--primary-700'); commonTokens.primaryColorSuppl = getCssVariableValue('--primary-800'); commonTokens.errorColor = getCssVariableValue('--destructive'); commonTokens.errorColorHover = getCssVariableValue('--destructive-600'); commonTokens.errorColorPressed = getCssVariableValue('--destructive-700'); commonTokens.errorColorSuppl = getCssVariableValue('--destructive-800'); commonTokens.warningColor = getCssVariableValue('--warning'); commonTokens.warningColorHover = getCssVariableValue('--warning-600'); commonTokens.warningColorPressed = getCssVariableValue('--warning-700'); commonTokens.warningColorSuppl = getCssVariableValue('--warning-800'); commonTokens.successColor = getCssVariableValue('--success'); commonTokens.successColorHover = getCssVariableValue('--success-600'); commonTokens.successColorPressed = getCssVariableValue('--success-700'); commonTokens.successColorSuppl = getCssVariableValue('--success-800'); commonTokens.textColorBase = getCssVariableValue('--foreground'); commonTokens.baseColor = getCssVariableValue('--primary-foreground'); commonTokens.dividerColor = commonTokens.borderColor = getCssVariableValue('--border'); commonTokens.modalColor = commonTokens.popoverColor = getCssVariableValue('--popover'); commonTokens.tableColor = commonTokens.cardColor = getCssVariableValue('--card'); commonTokens.bodyColor = getCssVariableValue('--background'); commonTokens.invertedColor = getCssVariableValue('--background-deep'); commonTokens.borderRadius = getCssVariableValue('--radius', false); }, { immediate: true }, ); return { commonTokens, }; } export function useElementPlusDesignTokens() { const { isDark } = usePreferences(); const rootStyles = getComputedStyle(document.documentElement); const getCssVariableValueRaw = (variable: string) => { return rootStyles.getPropertyValue(variable); }; const getCssVariableValue = (variable: string, isColor: boolean = true) => { const value = getCssVariableValueRaw(variable); return isColor ? convertToRgb(`hsl(${value})`) : value; }; watch( () => preferences.theme, () => { const background = getCssVariableValue('--background'); const border = getCssVariableValue('--border'); const accent = getCssVariableValue('--accent'); const variables: Record = { '--el-bg-color': background, '--el-bg-color-overlay': getCssVariableValue('--popover'), '--el-bg-color-page': getCssVariableValue('--background-deep'), '--el-border-color': border, '--el-border-color-dark': border, '--el-border-color-extra-light': border, '--el-border-color-hover': accent, '--el-border-color-light': border, '--el-border-color-lighter': border, '--el-border-radius-base': getCssVariableValue('--radius', false), '--el-color-danger': getCssVariableValue('--destructive-500'), '--el-color-danger-dark-2': isDark.value ? getCssVariableValue('--destructive-400') : getCssVariableValue('--destructive-600'), '--el-color-danger-light-3': isDark.value ? getCssVariableValue('--destructive-600') : getCssVariableValue('--destructive-400'), '--el-color-danger-light-5': isDark.value ? getCssVariableValue('--destructive-700') : getCssVariableValue('--destructive-300'), '--el-color-danger-light-7': isDark.value ? getCssVariableValue('--destructive-800') : getCssVariableValue('--destructive-200'), '--el-color-danger-light-8': isDark.value ? getCssVariableValue('--destructive-900') : getCssVariableValue('--destructive-100'), '--el-color-danger-light-9': isDark.value ? getCssVariableValue('--destructive-950') : getCssVariableValue('--destructive-50'), '--el-color-error': getCssVariableValue('--destructive-500'), '--el-color-error-dark-2': isDark.value ? getCssVariableValue('--destructive-400') : getCssVariableValue('--destructive-600'), '--el-color-error-light-3': isDark.value ? getCssVariableValue('--destructive-600') : getCssVariableValue('--destructive-400'), '--el-color-error-light-5': isDark.value ? getCssVariableValue('--destructive-700') : getCssVariableValue('--destructive-300'), '--el-color-error-light-7': isDark.value ? getCssVariableValue('--destructive-800') : getCssVariableValue('--destructive-200'), '--el-color-error-light-8': isDark.value ? getCssVariableValue('--destructive-900') : getCssVariableValue('--destructive-100'), '--el-color-error-light-9': isDark.value ? getCssVariableValue('--destructive-950') : getCssVariableValue('--destructive-50'), '--el-color-info-light-5': border, '--el-color-info-light-8': border, '--el-color-info-light-9': getCssVariableValue('--info'), // getCssVariableValue('--secondary'), '--el-color-primary': getCssVariableValue('--primary-500'), '--el-color-primary-dark-2': isDark.value ? getCssVariableValue('--primary-400') : getCssVariableValue('--primary-600'), '--el-color-primary-light-3': isDark.value ? getCssVariableValue('--primary-600') : getCssVariableValue('--primary-400'), '--el-color-primary-light-5': isDark.value ? getCssVariableValue('--primary-700') : getCssVariableValue('--primary-300'), '--el-color-primary-light-7': isDark.value ? getCssVariableValue('--primary-800') : getCssVariableValue('--primary-200'), '--el-color-primary-light-8': isDark.value ? getCssVariableValue('--primary-900') : getCssVariableValue('--primary-100'), '--el-color-primary-light-9': isDark.value ? getCssVariableValue('--primary-950') : getCssVariableValue('--primary-50'), '--el-color-success': getCssVariableValue('--success-500'), '--el-color-success-dark-2': isDark.value ? getCssVariableValue('--success-400') : getCssVariableValue('--success-600'), '--el-color-success-light-3': isDark.value ? getCssVariableValue('--success-600') : getCssVariableValue('--success-400'), '--el-color-success-light-5': isDark.value ? getCssVariableValue('--success-700') : getCssVariableValue('--success-300'), '--el-color-success-light-7': isDark.value ? getCssVariableValue('--success-800') : getCssVariableValue('--success-200'), '--el-color-success-light-8': isDark.value ? getCssVariableValue('--success-900') : getCssVariableValue('--success-100'), '--el-color-success-light-9': isDark.value ? getCssVariableValue('--success-950') : getCssVariableValue('--success-50'), '--el-color-warning': getCssVariableValue('--warning-500'), '--el-color-warning-dark-2': isDark.value ? getCssVariableValue('--warning-400') : getCssVariableValue('--warning-600'), '--el-color-warning-light-3': isDark.value ? getCssVariableValue('--warning-600') : getCssVariableValue('--warning-400'), '--el-color-warning-light-5': isDark.value ? getCssVariableValue('--warning-700') : getCssVariableValue('--warning-300'), '--el-color-warning-light-7': isDark.value ? getCssVariableValue('--warning-800') : getCssVariableValue('--warning-200'), '--el-color-warning-light-8': isDark.value ? getCssVariableValue('--warning-900') : getCssVariableValue('--warning-100'), '--el-color-warning-light-9': isDark.value ? getCssVariableValue('--warning-950') : getCssVariableValue('--warning-50'), '--el-fill-color': getCssVariableValue('--accent'), '--el-fill-color-blank': background, '--el-fill-color-light': getCssVariableValue('--accent'), '--el-fill-color-lighter': getCssVariableValue('--accent-lighter'), '--el-fill-color-dark': getCssVariableValue('--accent-dark'), '--el-fill-color-darker': getCssVariableValue('--accent-darker'), // 解决ElLoading背景色问题 '--el-mask-color': isDark.value ? 'rgba(0,0,0,.8)' : 'rgba(255,255,255,.9)', '--el-text-color-primary': getCssVariableValue('--foreground'), '--el-text-color-regular': getCssVariableValue('--foreground'), }; updateCSSVariables(variables, `__vben_design_styles__`); }, { immediate: true }, ); } ================================================ FILE: packages/effects/hooks/src/use-hover-toggle.ts ================================================ import type { Arrayable, MaybeElementRef } from '@vueuse/core'; import type { Ref } from 'vue'; import { computed, effectScope, onUnmounted, ref, unref, watch } from 'vue'; import { isFunction } from '@vben/utils'; import { useElementHover } from '@vueuse/core'; interface HoverDelayOptions { /** 鼠标进入延迟时间 */ enterDelay?: (() => number) | number; /** 鼠标离开延迟时间 */ leaveDelay?: (() => number) | number; } const DEFAULT_LEAVE_DELAY = 500; // 鼠标离开延迟时间,默认为 500ms const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0(立即响应) /** * 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false * @param refElement 所有需要检测的元素。支持单个元素、元素数组或响应式引用的元素数组。如果鼠标在任何一个元素内部都会返回 true * @param delay 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象 * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用 */ export function useHoverToggle( refElement: Arrayable | Ref, delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY, ) { // 兼容旧版本API const normalizedOptions: HoverDelayOptions = typeof delay === 'number' || isFunction(delay) ? { enterDelay: DEFAULT_ENTER_DELAY, leaveDelay: delay } : { enterDelay: DEFAULT_ENTER_DELAY, leaveDelay: DEFAULT_LEAVE_DELAY, ...delay, }; const value = ref(false); const enterTimer = ref | undefined>(); const leaveTimer = ref | undefined>(); const hoverScopes = ref[]>([]); // 使用计算属性包装 refElement,使其响应式变化 const refs = computed(() => { const raw = unref(refElement); if (raw === null) return []; return Array.isArray(raw) ? raw : [raw]; }); // 存储所有 hover 状态 const isHovers = ref>>([]); // 更新 hover 监听的函数 function updateHovers() { // 停止并清理之前的作用域 hoverScopes.value.forEach((scope) => scope.stop()); hoverScopes.value = []; isHovers.value = refs.value.map((refEle) => { if (!refEle) { return ref(false); } const eleRef = computed(() => { const ele = unref(refEle); return ele instanceof Element ? ele : (ele?.$el as Element); }); // 为每个元素创建独立的作用域 const scope = effectScope(); const hoverRef = scope.run(() => useElementHover(eleRef)) || ref(false); hoverScopes.value.push(scope); return hoverRef; }); } // 监听元素数量变化,避免过度执行 const elementsCount = computed(() => { const raw = unref(refElement); if (raw === null) return 0; return Array.isArray(raw) ? raw.length : 1; }); // 初始设置 updateHovers(); // 只在元素数量变化时重新设置监听器 const stopWatcher = watch(elementsCount, updateHovers, { deep: false }); const isOutsideAll = computed(() => isHovers.value.every((v) => !v.value)); function clearTimers() { if (enterTimer.value) { clearTimeout(enterTimer.value); enterTimer.value = undefined; } if (leaveTimer.value) { clearTimeout(leaveTimer.value); leaveTimer.value = undefined; } } function setValueDelay(val: boolean) { clearTimers(); if (val) { // 鼠标进入 const enterDelay = normalizedOptions.enterDelay ?? DEFAULT_ENTER_DELAY; const delayTime = isFunction(enterDelay) ? enterDelay() : enterDelay; if (delayTime <= 0) { value.value = true; } else { enterTimer.value = setTimeout(() => { value.value = true; enterTimer.value = undefined; }, delayTime); } } else { // 鼠标离开 const leaveDelay = normalizedOptions.leaveDelay ?? DEFAULT_LEAVE_DELAY; const delayTime = isFunction(leaveDelay) ? leaveDelay() : leaveDelay; if (delayTime <= 0) { value.value = false; } else { leaveTimer.value = setTimeout(() => { value.value = false; leaveTimer.value = undefined; }, delayTime); } } } const hoverWatcher = watch( isOutsideAll, (val) => { setValueDelay(!val); }, { immediate: true }, ); const controller = { enable() { hoverWatcher.resume(); }, disable() { hoverWatcher.pause(); }, }; onUnmounted(() => { clearTimers(); // 停止监听器 stopWatcher(); // 停止所有剩余的作用域 hoverScopes.value.forEach((scope) => scope.stop()); }); return [value, controller] as [typeof value, typeof controller]; } ================================================ FILE: packages/effects/hooks/src/use-pagination.ts ================================================ import type { Ref } from 'vue'; import { computed, ref, unref } from 'vue'; /** * Paginates an array of items * @param list The array to paginate * @param pageNo The current page number (1-based) * @param pageSize Number of items per page * @returns Paginated array slice * @throws {Error} If pageNo or pageSize are invalid */ function pagination(list: T[], pageNo: number, pageSize: number): T[] { if (pageNo < 1) throw new Error('Page number must be positive'); if (pageSize < 1) throw new Error('Page size must be positive'); const offset = (pageNo - 1) * Number(pageSize); const ret = offset + pageSize >= list.length ? list.slice(offset) : list.slice(offset, offset + pageSize); return ret; } export function usePagination(list: Ref, pageSize: number) { const currentPage = ref(1); const pageSizeRef = ref(pageSize); const totalPages = computed(() => Math.ceil(unref(list).length / unref(pageSizeRef)), ); const paginationList = computed(() => { return pagination(unref(list), unref(currentPage), unref(pageSizeRef)); }); const total = computed(() => { return unref(list).length; }); function setCurrentPage(page: number) { if (page < 1 || page > unref(totalPages)) { throw new Error('Invalid page number'); } currentPage.value = page; } function setPageSize(pageSize: number) { if (pageSize < 1) { throw new Error('Page size must be positive'); } pageSizeRef.value = pageSize; // Reset to first page to prevent invalid state currentPage.value = 1; } return { setCurrentPage, total, setPageSize, paginationList }; } ================================================ FILE: packages/effects/hooks/src/use-refresh.ts ================================================ import { useRouter } from 'vue-router'; import { useTabbarStore } from '@vben/stores'; export function useRefresh() { const router = useRouter(); const tabbarStore = useTabbarStore(); async function refresh() { await tabbarStore.refresh(router); } return { refresh, }; } ================================================ FILE: packages/effects/hooks/src/use-tabs.ts ================================================ import type { ComputedRef } from 'vue'; import type { RouteLocationNormalized } from 'vue-router'; import { useTabbarStore } from '@vben/stores'; import { useRoute, useRouter } from 'vue-router'; export function useTabs() { const router = useRouter(); const route = useRoute(); const tabbarStore = useTabbarStore(); async function closeLeftTabs(tab?: RouteLocationNormalized) { await tabbarStore.closeLeftTabs(tab || route); } async function closeAllTabs() { await tabbarStore.closeAllTabs(router); } async function closeRightTabs(tab?: RouteLocationNormalized) { await tabbarStore.closeRightTabs(tab || route); } async function closeOtherTabs(tab?: RouteLocationNormalized) { await tabbarStore.closeOtherTabs(tab || route); } async function closeCurrentTab(tab?: RouteLocationNormalized) { await tabbarStore.closeTab(tab || route, router); } async function pinTab(tab?: RouteLocationNormalized) { await tabbarStore.pinTab(tab || route); } async function unpinTab(tab?: RouteLocationNormalized) { await tabbarStore.unpinTab(tab || route); } async function toggleTabPin(tab?: RouteLocationNormalized) { await tabbarStore.toggleTabPin(tab || route); } async function refreshTab(name?: string) { await tabbarStore.refresh(name || router); } async function openTabInNewWindow(tab?: RouteLocationNormalized) { await tabbarStore.openTabInNewWindow(tab || route); } async function closeTabByKey(key: string) { await tabbarStore.closeTabByKey(key, router); } /** * 设置当前标签页的标题 * * @description 支持设置静态标题字符串或动态计算标题 * @description 动态标题会在每次渲染时重新计算,适用于多语言或状态相关的标题 * * @param title - 标题内容 * - 静态标题: 直接传入字符串 * - 动态标题: 传入 ComputedRef * * @example * // 静态标题 * setTabTitle('标签页') * * // 动态标题(多语言) * setTabTitle(computed(() => t('page.title'))) */ async function setTabTitle(title: ComputedRef | string) { tabbarStore.setUpdateTime(); await tabbarStore.setTabTitle(route, title); } async function resetTabTitle() { tabbarStore.setUpdateTime(); await tabbarStore.resetTabTitle(route); } /** * 获取操作是否禁用 * @param tab */ function getTabDisableState(tab: RouteLocationNormalized = route) { const tabs = tabbarStore.getTabs; const affixTabs = tabbarStore.affixTabs; const index = tabs.findIndex((item) => item.path === tab.path); const disabled = tabs.length <= 1; const { meta } = tab; const affixTab = meta?.affixTab ?? false; const isCurrentTab = route.path === tab.path; // 当前处于最左侧或者减去固定标签页的数量等于0 const disabledCloseLeft = index === 0 || index - affixTabs.length <= 0 || !isCurrentTab; const disabledCloseRight = !isCurrentTab || index === tabs.length - 1; const disabledCloseOther = disabled || !isCurrentTab || tabs.length - affixTabs.length <= 1; return { disabledCloseAll: disabled, disabledCloseCurrent: !!affixTab || disabled, disabledCloseLeft, disabledCloseOther, disabledCloseRight, disabledRefresh: !isCurrentTab, }; } return { closeAllTabs, closeCurrentTab, closeLeftTabs, closeOtherTabs, closeRightTabs, closeTabByKey, getTabDisableState, openTabInNewWindow, pinTab, refreshTab, resetTabTitle, setTabTitle, toggleTabPin, unpinTab, }; } ================================================ FILE: packages/effects/hooks/src/use-watermark.ts ================================================ import type { Watermark, WatermarkOptions } from 'watermark-js-plus'; import { nextTick, onUnmounted, readonly, ref } from 'vue'; const watermark = ref(); const unmountedHooked = ref(false); const cachedOptions = ref>({ advancedStyle: { colorStops: [ { color: 'gray', offset: 0, }, { color: 'gray', offset: 1, }, ], type: 'linear', }, // fontSize: '20px', content: '', contentType: 'multi-line-text', globalAlpha: 0.25, gridLayoutOptions: { cols: 2, gap: [20, 20], matrix: [ [1, 0], [0, 1], ], rows: 2, }, height: 200, layout: 'grid', rotate: 30, width: 160, }); export function useWatermark() { async function initWatermark(options: Partial) { const { Watermark } = await import('watermark-js-plus'); cachedOptions.value = { ...cachedOptions.value, ...options, }; watermark.value = new Watermark(cachedOptions.value); await watermark.value?.create(); } async function updateWatermark(options: Partial) { if (watermark.value) { await nextTick(); await watermark.value?.changeOptions({ ...cachedOptions.value, ...options, }); } else { await initWatermark(options); } } function destroyWatermark() { if (watermark.value) { watermark.value.destroy(); watermark.value = undefined; } } // 只在第一次调用时注册卸载钩子,防止重复注册以致于在路由切换时销毁了水印 if (!unmountedHooked.value) { unmountedHooked.value = true; onUnmounted(() => { destroyWatermark(); }); } return { destroyWatermark, updateWatermark, watermark: readonly(watermark), }; } ================================================ FILE: packages/effects/hooks/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "compilerOptions": { "types": ["vite/client", "@vben/types/global"] }, "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/effects/layouts/package.json ================================================ { "name": "@vben/layouts", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/effects/layouts" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben-core/composables": "workspace:*", "@vben-core/form-ui": "workspace:*", "@vben-core/layout-ui": "workspace:*", "@vben-core/menu-ui": "workspace:*", "@vben-core/popup-ui": "workspace:*", "@vben-core/shadcn-ui": "workspace:*", "@vben-core/shared": "workspace:*", "@vben-core/tabs-ui": "workspace:*", "@vben/constants": "workspace:*", "@vben/hooks": "workspace:*", "@vben/icons": "workspace:*", "@vben/locales": "workspace:*", "@vben/preferences": "workspace:*", "@vben/stores": "workspace:*", "@vben/types": "workspace:*", "@vben/utils": "workspace:*", "@vueuse/core": "catalog:", "vue": "catalog:", "vue-router": "catalog:" } } ================================================ FILE: packages/effects/layouts/src/authentication/authentication.vue ================================================ ================================================ FILE: packages/effects/layouts/src/authentication/form.vue ================================================ ================================================ FILE: packages/effects/layouts/src/authentication/icons/slogan.vue ================================================ ================================================ FILE: packages/effects/layouts/src/authentication/index.ts ================================================ export { default as AuthPageLayout } from './authentication.vue'; export * from './types'; ================================================ FILE: packages/effects/layouts/src/authentication/toolbar.vue ================================================ ================================================ FILE: packages/effects/layouts/src/authentication/types.ts ================================================ export type ToolbarType = 'color' | 'language' | 'layout' | 'theme'; ================================================ FILE: packages/effects/layouts/src/basic/README.md ================================================ ## layout ### header - 支持N个自定义插槽,命名方式:header-right-n,header-left-n - header-left-n ,排序方式:0-19 ,breadcrumb 21-x - header-right-n ,排序方式:0-49,global-search,51-59,theme-toggle,61-69,language-toggle,71-79,fullscreen,81-89,notification,91-149,user-dropdown,151-x ================================================ FILE: packages/effects/layouts/src/basic/content/content-spinner.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/content/content.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/content/index.ts ================================================ export { default as LayoutContent } from './content.vue'; export { default as LayoutContentSpinner } from './content-spinner.vue'; ================================================ FILE: packages/effects/layouts/src/basic/content/use-content-spinner.ts ================================================ import { computed, ref } from 'vue'; import { useRouter } from 'vue-router'; import { preferences } from '@vben/preferences'; function useContentSpinner() { const spinning = ref(false); const startTime = ref(0); const router = useRouter(); const minShowTime = 500; // 最小显示时间 const enableLoading = computed(() => preferences.transition.loading); // 结束加载动画 const onEnd = () => { if (!enableLoading.value) { return; } const processTime = performance.now() - startTime.value; if (processTime < minShowTime) { setTimeout(() => { spinning.value = false; }, minShowTime - processTime); } else { spinning.value = false; } }; // 路由前置守卫 router.beforeEach((to) => { if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) { return true; } startTime.value = performance.now(); spinning.value = true; return true; }); // 路由后置守卫 router.afterEach((to) => { if (to.meta.loaded || !enableLoading.value || to.meta.iframeSrc) { return true; } onEnd(); return true; }); return { spinning }; } export { useContentSpinner }; ================================================ FILE: packages/effects/layouts/src/basic/copyright/copyright.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/copyright/index.ts ================================================ export { default as Copyright } from './copyright.vue'; ================================================ FILE: packages/effects/layouts/src/basic/footer/footer.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/footer/index.ts ================================================ export { default as LayoutFooter } from './footer.vue'; ================================================ FILE: packages/effects/layouts/src/basic/header/header.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/header/index.ts ================================================ export { default as LayoutHeader } from './header.vue'; ================================================ FILE: packages/effects/layouts/src/basic/index.ts ================================================ export { default as BasicLayout } from './layout.vue'; ================================================ FILE: packages/effects/layouts/src/basic/layout.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/menu/extra-menu.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/menu/index.ts ================================================ export { default as LayoutExtraMenu } from './extra-menu.vue'; export { default as LayoutMenu } from './menu.vue'; export { default as LayoutMixedMenu } from './mixed-menu.vue'; export * from './use-extra-menu'; export * from './use-mixed-menu'; ================================================ FILE: packages/effects/layouts/src/basic/menu/menu.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/menu/mixed-menu.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/menu/use-extra-menu.ts ================================================ import type { ComputedRef } from 'vue'; import type { MenuRecordRaw } from '@vben/types'; import { computed, ref, watch } from 'vue'; import { useRoute } from 'vue-router'; import { preferences } from '@vben/preferences'; import { useAccessStore } from '@vben/stores'; import { findRootMenuByPath } from '@vben/utils'; import { useNavigation } from './use-navigation'; function useExtraMenu(useRootMenus?: ComputedRef) { const accessStore = useAccessStore(); const { navigation, willOpenedByWindow } = useNavigation(); const menus = computed(() => useRootMenus?.value ?? accessStore.accessMenus); /** 记录当前顶级菜单下哪个子菜单最后激活 */ const defaultSubMap = new Map(); const extraRootMenus = ref([]); const route = useRoute(); const extraMenus = ref([]); const sidebarExtraVisible = ref(false); const extraActiveMenu = ref(''); const parentLevel = computed(() => preferences.app.layout === 'header-mixed-nav' ? 1 : 0, ); /** * 选择混合菜单事件 * @param menu */ const handleMixedMenuSelect = async (menu: MenuRecordRaw) => { const _extraMenus = menu?.children ?? []; const hasChildren = _extraMenus.length > 0; if (!willOpenedByWindow(menu.path)) { extraMenus.value = _extraMenus ?? []; extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path; sidebarExtraVisible.value = hasChildren; } if (!hasChildren) { await navigation(menu.path); } else if (preferences.sidebar.autoActivateChild) { await navigation( defaultSubMap.has(menu.path) ? (defaultSubMap.get(menu.path) as string) : menu.path, ); } }; /** * 选择默认菜单事件 * @param menu * @param rootMenu */ const handleDefaultSelect = async ( menu: MenuRecordRaw, rootMenu?: MenuRecordRaw, ) => { extraMenus.value = rootMenu?.children ?? extraRootMenus.value ?? []; extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path; if (preferences.sidebar.expandOnHover) { sidebarExtraVisible.value = extraMenus.value.length > 0; } }; /** * 侧边菜单鼠标移出事件 */ const handleSideMouseLeave = () => { if (preferences.sidebar.expandOnHover) { return; } const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath( menus.value, route.path, ); extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? ''; extraMenus.value = rootMenu?.children ?? []; }; const handleMenuMouseEnter = (menu: MenuRecordRaw) => { if (!preferences.sidebar.expandOnHover) { const { findMenu } = findRootMenuByPath(menus.value, menu.path); extraMenus.value = findMenu?.children ?? []; extraActiveMenu.value = menu.parents?.[parentLevel.value] ?? menu.path; sidebarExtraVisible.value = extraMenus.value.length > 0; } }; function calcExtraMenus(path: string) { const currentPath = route.meta?.activePath || path; const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath( menus.value, currentPath, parentLevel.value, ); extraRootMenus.value = rootMenu?.children ?? []; if (rootMenuPath) defaultSubMap.set(rootMenuPath, currentPath); extraActiveMenu.value = rootMenuPath ?? findMenu?.path ?? ''; extraMenus.value = rootMenu?.children ?? []; if (preferences.sidebar.expandOnHover) { sidebarExtraVisible.value = extraMenus.value.length > 0; } } watch( () => [route.path, preferences.app.layout], ([path]) => { calcExtraMenus(path || ''); }, { immediate: true }, ); return { extraActiveMenu, extraMenus, handleDefaultSelect, handleMenuMouseEnter, handleMixedMenuSelect, handleSideMouseLeave, sidebarExtraVisible, }; } export { useExtraMenu }; ================================================ FILE: packages/effects/layouts/src/basic/menu/use-mixed-menu.ts ================================================ import type { MenuRecordRaw } from '@vben/types'; import { computed, onBeforeMount, ref, watch } from 'vue'; import { useRoute } from 'vue-router'; import { preferences, usePreferences } from '@vben/preferences'; import { useAccessStore } from '@vben/stores'; import { findRootMenuByPath } from '@vben/utils'; import { useNavigation } from './use-navigation'; function useMixedMenu() { const { navigation, willOpenedByWindow } = useNavigation(); const accessStore = useAccessStore(); const route = useRoute(); const splitSideMenus = ref([]); const rootMenuPath = ref(''); const mixedRootMenuPath = ref(''); const mixExtraMenus = ref([]); /** 记录当前顶级菜单下哪个子菜单最后激活 */ const defaultSubMap = new Map(); const { isMixedNav, isHeaderMixedNav } = usePreferences(); const needSplit = computed( () => (preferences.navigation.split && isMixedNav.value) || isHeaderMixedNav.value, ); const sidebarVisible = computed(() => { const enableSidebar = preferences.sidebar.enable; if (needSplit.value) { return enableSidebar && splitSideMenus.value.length > 0; } return enableSidebar; }); const menus = computed(() => accessStore.accessMenus); /** * 头部菜单 */ const headerMenus = computed(() => { if (!needSplit.value) { return menus.value; } return menus.value.map((item) => { return { ...item, children: [], }; }); }); /** * 侧边菜单 */ const sidebarMenus = computed(() => { return needSplit.value ? splitSideMenus.value : menus.value; }); const mixHeaderMenus = computed(() => { return isHeaderMixedNav.value ? sidebarMenus.value : headerMenus.value; }); /** * 侧边菜单激活路径 */ const sidebarActive = computed(() => { return (route?.meta?.activePath as string) ?? route.path; }); /** * 头部菜单激活路径 */ const headerActive = computed(() => { if (!needSplit.value) { return route.meta?.activePath ?? route.path; } return rootMenuPath.value; }); /** * 菜单点击事件处理 * @param key 菜单路径 * @param mode 菜单模式 */ const handleMenuSelect = (key: string, mode?: string) => { if (!needSplit.value || mode === 'vertical') { navigation(key); return; } const rootMenu = menus.value.find((item) => item.path === key); const _splitSideMenus = rootMenu?.children ?? []; if (!willOpenedByWindow(key)) { rootMenuPath.value = rootMenu?.path ?? ''; splitSideMenus.value = _splitSideMenus; } if (_splitSideMenus.length === 0) { navigation(key); } else if (rootMenu && preferences.sidebar.autoActivateChild) { navigation( defaultSubMap.has(rootMenu.path) ? (defaultSubMap.get(rootMenu.path) as string) : rootMenu.path, ); } }; /** * 侧边菜单展开事件 * @param key 路由路径 * @param parentsPath 父级路径 */ const handleMenuOpen = (key: string, parentsPath: string[]) => { if (parentsPath.length <= 1 && preferences.sidebar.autoActivateChild) { navigation( defaultSubMap.has(key) ? (defaultSubMap.get(key) as string) : key, ); } }; /** * 计算侧边菜单 * @param path 路由路径 */ function calcSideMenus(path: string = route.path) { let { rootMenu } = findRootMenuByPath(menus.value, path); if (!rootMenu) { rootMenu = menus.value.find((item) => item.path === path); } const result = findRootMenuByPath(rootMenu?.children || [], path, 1); mixedRootMenuPath.value = result.rootMenuPath ?? ''; mixExtraMenus.value = result.rootMenu?.children ?? []; rootMenuPath.value = rootMenu?.path ?? ''; splitSideMenus.value = rootMenu?.children ?? []; } watch( () => route.path, (path) => { const currentPath = route?.meta?.activePath ?? route?.meta?.link ?? path; if (willOpenedByWindow(currentPath)) { return; } calcSideMenus(currentPath); if (rootMenuPath.value) defaultSubMap.set(rootMenuPath.value, currentPath); }, { immediate: true }, ); // 初始化计算侧边菜单 onBeforeMount(() => { calcSideMenus(route.meta?.activePath || route.path); }); return { handleMenuSelect, handleMenuOpen, headerActive, headerMenus, sidebarActive, sidebarMenus, mixHeaderMenus, mixExtraMenus, sidebarVisible, }; } export { useMixedMenu }; ================================================ FILE: packages/effects/layouts/src/basic/menu/use-navigation.ts ================================================ import type { RouteRecordNormalized } from 'vue-router'; import { useRouter } from 'vue-router'; import { isHttpUrl, openRouteInNewWindow, openWindow } from '@vben/utils'; function useNavigation() { const router = useRouter(); const routeMetaMap = new Map(); // 初始化路由映射 const initRouteMetaMap = () => { const routes = router.getRoutes(); routes.forEach((route) => { routeMetaMap.set(route.path, route); }); }; initRouteMetaMap(); // 监听路由变化 router.afterEach(() => { initRouteMetaMap(); }); // 检查是否应该在新窗口打开 const shouldOpenInNewWindow = (path: string): boolean => { if (isHttpUrl(path)) { return true; } const route = routeMetaMap.get(path); // 如果有外链或者设置了在新窗口打开,返回 true return !!(route?.meta?.link || route?.meta?.openInNewWindow); }; const resolveHref = (path: string): string => { return router.resolve(path).href; }; const navigation = async (path: string) => { try { const route = routeMetaMap.get(path); const { openInNewWindow = false, query = {}, link } = route?.meta ?? {}; // 检查是否有外链 if (link && typeof link === 'string') { openWindow(link, { target: '_blank' }); return; } if (isHttpUrl(path)) { openWindow(path, { target: '_blank' }); } else if (openInNewWindow) { openRouteInNewWindow(resolveHref(path)); } else { await router.push({ path, query, }); } } catch (error) { console.error('Navigation failed:', error); throw error; } }; const willOpenedByWindow = (path: string) => { return shouldOpenInNewWindow(path); }; return { navigation, willOpenedByWindow }; } export { useNavigation }; ================================================ FILE: packages/effects/layouts/src/basic/tabbar/index.ts ================================================ export { default as LayoutTabbar } from './tabbar.vue'; export * from './use-tabbar'; ================================================ FILE: packages/effects/layouts/src/basic/tabbar/tabbar.vue ================================================ ================================================ FILE: packages/effects/layouts/src/basic/tabbar/use-tabbar.ts ================================================ import type { RouteLocationNormalizedGeneric } from 'vue-router'; import type { TabDefinition } from '@vben/types'; import type { IContextMenuItem } from '@vben-core/tabs-ui'; import { computed, ref, watch } from 'vue'; import { useRoute, useRouter } from 'vue-router'; import { useContentMaximize, useTabs } from '@vben/hooks'; import { ArrowLeftToLine, ArrowRightLeft, ArrowRightToLine, ExternalLink, FoldHorizontal, Fullscreen, Minimize2, Pin, PinOff, RotateCw, X, } from '@vben/icons'; import { $t, useI18n } from '@vben/locales'; import { getTabKey, useAccessStore, useTabbarStore } from '@vben/stores'; import { filterTree } from '@vben/utils'; export function useTabbar() { const router = useRouter(); const route = useRoute(); const accessStore = useAccessStore(); const tabbarStore = useTabbarStore(); const { contentIsMaximize, toggleMaximize } = useContentMaximize(); const { closeAllTabs, closeCurrentTab, closeLeftTabs, closeOtherTabs, closeRightTabs, closeTabByKey, getTabDisableState, openTabInNewWindow, refreshTab, toggleTabPin, } = useTabs(); /** * 当前路径对应的tab的key */ const currentActive = computed(() => { return getTabKey(route); }); const { locale } = useI18n(); const currentTabs = ref(); watch( [ () => tabbarStore.getTabs, () => tabbarStore.updateTime, () => locale.value, ], ([tabs]) => { currentTabs.value = tabs.map((item) => wrapperTabLocale(item)); }, ); /** * 初始化固定标签页 */ const initAffixTabs = () => { const affixTabs = filterTree(router.getRoutes(), (route) => { return !!route.meta?.affixTab; }); tabbarStore.setAffixTabs(affixTabs); }; // 点击tab,跳转路由 const handleClick = (key: string) => { const { fullPath, path } = tabbarStore.getTabByKey(key); router.push(fullPath || path); }; // 关闭tab const handleClose = async (key: string) => { await closeTabByKey(key); }; function wrapperTabLocale(tab: RouteLocationNormalizedGeneric) { return { ...tab, meta: { ...tab?.meta, title: $t(tab?.meta?.title as string), }, }; } watch( () => accessStore.accessMenus, () => { initAffixTabs(); }, { immediate: true }, ); watch( () => route.fullPath, () => { const meta = route.matched?.[route.matched.length - 1]?.meta; tabbarStore.addTab({ ...route, meta: meta || route.meta, }); }, { immediate: true }, ); const createContextMenus = (tab: TabDefinition) => { const { disabledCloseAll, disabledCloseCurrent, disabledCloseLeft, disabledCloseOther, disabledCloseRight, disabledRefresh, } = getTabDisableState(tab); const affixTab = tab?.meta?.affixTab ?? false; const menus: IContextMenuItem[] = [ { disabled: disabledCloseCurrent, handler: async () => { await closeCurrentTab(tab); }, icon: X, key: 'close', text: $t('preferences.tabbar.contextMenu.close'), }, { handler: async () => { await toggleTabPin(tab); }, icon: affixTab ? PinOff : Pin, key: 'affix', text: affixTab ? $t('preferences.tabbar.contextMenu.unpin') : $t('preferences.tabbar.contextMenu.pin'), }, { handler: async () => { if (!contentIsMaximize.value) { await router.push(tab.fullPath); } toggleMaximize(); }, icon: contentIsMaximize.value ? Minimize2 : Fullscreen, key: contentIsMaximize.value ? 'restore-maximize' : 'maximize', text: contentIsMaximize.value ? $t('preferences.tabbar.contextMenu.restoreMaximize') : $t('preferences.tabbar.contextMenu.maximize'), }, { disabled: disabledRefresh, handler: () => refreshTab(), icon: RotateCw, key: 'reload', text: $t('preferences.tabbar.contextMenu.reload'), }, { handler: async () => { await openTabInNewWindow(tab); }, icon: ExternalLink, key: 'open-in-new-window', separator: true, text: $t('preferences.tabbar.contextMenu.openInNewWindow'), }, { disabled: disabledCloseLeft, handler: async () => { await closeLeftTabs(tab); }, icon: ArrowLeftToLine, key: 'close-left', text: $t('preferences.tabbar.contextMenu.closeLeft'), }, { disabled: disabledCloseRight, handler: async () => { await closeRightTabs(tab); }, icon: ArrowRightToLine, key: 'close-right', separator: true, text: $t('preferences.tabbar.contextMenu.closeRight'), }, { disabled: disabledCloseOther, handler: async () => { await closeOtherTabs(tab); }, icon: FoldHorizontal, key: 'close-other', text: $t('preferences.tabbar.contextMenu.closeOther'), }, { disabled: disabledCloseAll, handler: closeAllTabs, icon: ArrowRightLeft, key: 'close-all', text: $t('preferences.tabbar.contextMenu.closeAll'), }, ]; return menus.filter((item) => tabbarStore.getMenuList.includes(item.key)); }; return { createContextMenus, currentActive, currentTabs, handleClick, handleClose, }; } ================================================ FILE: packages/effects/layouts/src/iframe/iframe-router-view.vue ================================================ ================================================ FILE: packages/effects/layouts/src/iframe/iframe-view.vue ================================================ ================================================ FILE: packages/effects/layouts/src/iframe/index.ts ================================================ export { default as IFrameRouterView } from './iframe-router-view.vue'; export { default as IFrameView } from './iframe-view.vue'; ================================================ FILE: packages/effects/layouts/src/index.ts ================================================ export * from './authentication'; export * from './basic'; export * from './iframe'; export * from './widgets'; ================================================ FILE: packages/effects/layouts/src/widgets/breadcrumb.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/check-updates/check-updates.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/check-updates/index.ts ================================================ export { default as CheckUpdates } from './check-updates.vue'; ================================================ FILE: packages/effects/layouts/src/widgets/color-toggle.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/global-search/global-search.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/global-search/index.ts ================================================ export { default as GlobalSearch } from './global-search.vue'; ================================================ FILE: packages/effects/layouts/src/widgets/global-search/search-panel.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/index.ts ================================================ export { default as Breadcrumb } from './breadcrumb.vue'; export * from './check-updates'; export { default as AuthenticationColorToggle } from './color-toggle.vue'; export * from './global-search'; export { default as LanguageToggle } from './language-toggle.vue'; export { default as AuthenticationLayoutToggle } from './layout-toggle.vue'; export * from './lock-screen'; export * from './notification'; export * from './preferences'; export * from './theme-toggle'; export * from './user-dropdown'; ================================================ FILE: packages/effects/layouts/src/widgets/language-toggle.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/layout-toggle.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/lock-screen/index.ts ================================================ export { default as LockScreen } from './lock-screen.vue'; export { default as LockScreenModal } from './lock-screen-modal.vue'; ================================================ FILE: packages/effects/layouts/src/widgets/lock-screen/lock-screen-modal.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/lock-screen/lock-screen.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/notification/index.ts ================================================ export { default as Notification } from './notification.vue'; export type * from './types'; ================================================ FILE: packages/effects/layouts/src/widgets/notification/notification.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/notification/types.ts ================================================ interface NotificationItem { avatar: string; date: string; isRead?: boolean; message: string; title: string; userId: number | string; } export type { NotificationItem }; ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/block.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/checkbox-item.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/general/animation.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/general/general.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/index.ts ================================================ export { default as Block } from './block.vue'; export { default as Animation } from './general/animation.vue'; export { default as General } from './general/general.vue'; export { default as Breadcrumb } from './layout/breadcrumb.vue'; export { default as Content } from './layout/content.vue'; export { default as Copyright } from './layout/copyright.vue'; export { default as Footer } from './layout/footer.vue'; export { default as Header } from './layout/header.vue'; export { default as Layout } from './layout/layout.vue'; export { default as Navigation } from './layout/navigation.vue'; export { default as Sidebar } from './layout/sidebar.vue'; export { default as Tabbar } from './layout/tabbar.vue'; export { default as Widget } from './layout/widget.vue'; export { default as GlobalShortcutKeys } from './shortcut-keys/global.vue'; export { default as SwitchItem } from './switch-item.vue'; export { default as BuiltinTheme } from './theme/builtin.vue'; export { default as ColorMode } from './theme/color-mode.vue'; export { default as Radius } from './theme/radius.vue'; export { default as Theme } from './theme/theme.vue'; ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/input-item.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/breadcrumb.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/content.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/copyright.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/footer.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/header.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/layout.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/navigation.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/sidebar.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/tabbar.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/layout/widget.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/number-field-item.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/select-item.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/shortcut-keys/global.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/switch-item.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/theme/builtin.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/theme/color-mode.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/theme/radius.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/theme/theme.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/blocks/toggle-item.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/content-compact.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/full-content.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/header-mixed-nav.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/header-nav.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/header-sidebar-nav.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/index.ts ================================================ import HeaderNav from './header-nav.vue'; export { default as ContentCompact } from './content-compact.vue'; export { default as FullContent } from './full-content.vue'; export { default as HeaderMixedNav } from './header-mixed-nav.vue'; export { default as HeaderSidebarNav } from './header-sidebar-nav.vue'; export { default as MixedNav } from './mixed-nav.vue'; export { default as SidebarMixedNav } from './sidebar-mixed-nav.vue'; export { default as SidebarNav } from './sidebar-nav.vue'; const ContentWide = HeaderNav; export { ContentWide, HeaderNav }; ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/mixed-nav.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/setting.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/sidebar-mixed-nav.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/icons/sidebar-nav.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/index.ts ================================================ export { default as Preferences } from './preferences.vue'; export { default as PreferencesButton } from './preferences-button.vue'; export * from './use-open-preferences'; ================================================ FILE: packages/effects/layouts/src/widgets/preferences/preferences-button.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/preferences-drawer.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/preferences.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/preferences/use-open-preferences.ts ================================================ import { ref } from 'vue'; const openPreferences = ref(false); function useOpenPreferences() { function handleOpenPreference() { openPreferences.value = true; } return { handleOpenPreference, openPreferences, }; } export { useOpenPreferences }; ================================================ FILE: packages/effects/layouts/src/widgets/theme-toggle/index.ts ================================================ export { default as ThemeToggle } from './theme-toggle.vue'; ================================================ FILE: packages/effects/layouts/src/widgets/theme-toggle/theme-button.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/theme-toggle/theme-toggle.vue ================================================ ================================================ FILE: packages/effects/layouts/src/widgets/user-dropdown/index.ts ================================================ export { default as UserDropdown } from './user-dropdown.vue'; ================================================ FILE: packages/effects/layouts/src/widgets/user-dropdown/user-dropdown.vue ================================================ ================================================ FILE: packages/effects/layouts/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/effects/plugins/README.md ================================================ # @vben/plugins 该目录用于存放项目中集成的第三方库及其相关插件。每个插件都包含了可重用的逻辑、配置和组件,方便在项目中进行统一管理和调用。 ## 注意 所有的第三方插件都必须以 `subpath` 形式引入,例: 以 `echarts` 为例,引入方式如下: **packages.json** ```json "exports": { "./echarts": { "types": "./src/echarts/index.ts", "default": "./src/echarts/index.ts" } } ``` **使用方式** ```ts import { useEcharts } from '@vben/plugins/echarts'; ``` 这样做的好处是,应用可以自行选择是否使用插件,而不会因为插件的引入及副作用而导致打包体积增大,只引入需要的插件即可。 ================================================ FILE: packages/effects/plugins/package.json ================================================ { "name": "@vben/plugins", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/effects/plugins" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { "./echarts": { "types": "./src/echarts/index.ts", "default": "./src/echarts/index.ts" }, "./vxe-table": { "types": "./src/vxe-table/index.ts", "default": "./src/vxe-table/index.ts" }, "./motion": { "types": "./src/motion/index.ts", "default": "./src/motion/index.ts" } }, "dependencies": { "@vben-core/form-ui": "workspace:*", "@vben-core/shadcn-ui": "workspace:*", "@vben-core/shared": "workspace:*", "@vben/hooks": "workspace:*", "@vben/icons": "workspace:*", "@vben/locales": "workspace:*", "@vben/preferences": "workspace:*", "@vben/types": "workspace:*", "@vben/utils": "workspace:*", "@vueuse/core": "catalog:", "@vueuse/motion": "catalog:", "@vxe-ui/plugin-render-antd": "^4.0.18", "echarts": "catalog:", "vue": "catalog:", "vxe-pc-ui": "catalog:", "vxe-table": "catalog:" } } ================================================ FILE: packages/effects/plugins/src/echarts/echarts-ui.vue ================================================ ================================================ FILE: packages/effects/plugins/src/echarts/echarts.ts ================================================ import type { // 系列类型的定义后缀都为 SeriesOption BarSeriesOption, LineSeriesOption, } from 'echarts/charts'; import type { DatasetComponentOption, GridComponentOption, // 组件类型的定义后缀都为 ComponentOption TitleComponentOption, TooltipComponentOption, } from 'echarts/components'; import type { ComposeOption } from 'echarts/core'; import { BarChart, GaugeChart, LineChart, MapChart, PieChart, RadarChart, } from 'echarts/charts'; import { // 数据集组件 DatasetComponent, GridComponent, LegendComponent, TitleComponent, ToolboxComponent, TooltipComponent, // 内置数据转换器组件 (filter, sort) TransformComponent, VisualMapComponent, } from 'echarts/components'; import * as echarts from 'echarts/core'; import { LabelLayout, UniversalTransition } from 'echarts/features'; import { CanvasRenderer } from 'echarts/renderers'; // 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型 export type ECOption = ComposeOption< | BarSeriesOption | DatasetComponentOption | GridComponentOption | LineSeriesOption | TitleComponentOption | TooltipComponentOption >; // 注册必须的组件 echarts.use([ TitleComponent, PieChart, RadarChart, TooltipComponent, GridComponent, DatasetComponent, TransformComponent, BarChart, LineChart, LabelLayout, UniversalTransition, CanvasRenderer, LegendComponent, ToolboxComponent, GaugeChart, VisualMapComponent, MapChart, ]); export default echarts; ================================================ FILE: packages/effects/plugins/src/echarts/index.ts ================================================ export * from './echarts'; export { default as EchartsUI } from './echarts-ui.vue'; export * from './use-echarts'; ================================================ FILE: packages/effects/plugins/src/echarts/use-echarts.ts ================================================ import type { EChartsOption } from 'echarts'; import type { Ref } from 'vue'; import type { Nullable } from '@vben/types'; import type EchartsUI from './echarts-ui.vue'; import { computed, nextTick, watch } from 'vue'; import { usePreferences } from '@vben/preferences'; import { tryOnUnmounted, useDebounceFn, useResizeObserver, useTimeoutFn, useWindowSize, } from '@vueuse/core'; import echarts from './echarts'; type EchartsUIType = typeof EchartsUI | undefined; type EchartsThemeType = 'dark' | 'light' | null; function useEcharts(chartRef: Ref) { let chartInstance: echarts.ECharts | null = null; let cacheOptions: EChartsOption = {}; const { isDark } = usePreferences(); const { height, width } = useWindowSize(); const resizeHandler: () => void = useDebounceFn(resize, 200); const getChartEl = (): HTMLElement | null => { const refValue = chartRef?.value as unknown; if (!refValue) return null; if (refValue instanceof HTMLElement) { return refValue; } const maybeComponent = refValue as { $el?: HTMLElement }; return maybeComponent.$el ?? null; }; const isElHidden = (el: HTMLElement | null): boolean => { if (!el) return true; return el.offsetHeight === 0 || el.offsetWidth === 0; }; const getOptions = computed((): EChartsOption => { if (!isDark.value) { return {}; } return { backgroundColor: 'transparent', }; }); const initCharts = (t?: EchartsThemeType) => { const el = chartRef?.value?.$el; if (!el) { return; } chartInstance = echarts.init(el, t || isDark.value ? 'dark' : null); return chartInstance; }; const renderEcharts = ( options: EChartsOption, clear = true, ): Promise> => { cacheOptions = options; const currentOptions = { ...options, ...getOptions.value, }; return new Promise((resolve) => { if (chartRef.value?.offsetHeight === 0) { useTimeoutFn(async () => { resolve(await renderEcharts(currentOptions)); }, 30); return; } nextTick(() => { const el = getChartEl(); if (isElHidden(el)) { useTimeoutFn(async () => { resolve(await renderEcharts(currentOptions)); }, 30); return; } useTimeoutFn(() => { if (!chartInstance) { const instance = initCharts(); if (!instance) return; } clear && chartInstance?.clear(); chartInstance?.setOption(currentOptions); resolve(chartInstance); }, 30); }); }); }; function resize(withAnimation = true) { const el = getChartEl(); if (isElHidden(el)) { return; } chartInstance?.resize({ animation: withAnimation ? { duration: 300, easing: 'quadraticIn', } : undefined, }); } watch([width, height], () => { resizeHandler?.(); }); useResizeObserver(chartRef as never, resizeHandler); watch(isDark, () => { if (chartInstance) { chartInstance.dispose(); initCharts(); renderEcharts(cacheOptions); resize(); } }); tryOnUnmounted(() => { // 销毁实例,释放资源 chartInstance?.dispose(); }); return { renderEcharts, resize, getChartInstance: () => chartInstance, }; } export { useEcharts }; export type { EchartsUIType }; ================================================ FILE: packages/effects/plugins/src/motion/index.ts ================================================ export * from './types'; export { MotionComponent as Motion, MotionDirective, MotionGroupComponent as MotionGroup, MotionPlugin, } from '@vueuse/motion'; ================================================ FILE: packages/effects/plugins/src/motion/types.ts ================================================ export const MotionPresets = [ 'fade', 'fadeVisible', 'fadeVisibleOnce', 'rollBottom', 'rollLeft', 'rollRight', 'rollTop', 'rollVisibleBottom', 'rollVisibleLeft', 'rollVisibleRight', 'rollVisibleTop', 'pop', 'popVisible', 'popVisibleOnce', 'slideBottom', 'slideLeft', 'slideRight', 'slideTop', 'slideVisibleBottom', 'slideVisibleLeft', 'slideVisibleRight', 'slideVisibleTop', ] as const; export type MotionPreset = (typeof MotionPresets)[number]; ================================================ FILE: packages/effects/plugins/src/vxe-table/api.ts ================================================ import type { VxeGridInstance } from 'vxe-table'; import type { ExtendedFormApi } from '@vben-core/form-ui'; import type { VxeGridProps } from './types'; import { toRaw } from 'vue'; import { Store } from '@vben-core/shared/store'; import { bindMethods, isBoolean, isFunction, mergeWithArrayOverride, StateHandler, } from '@vben-core/shared/utils'; function getDefaultState(): VxeGridProps { return { class: '', gridClass: '', gridOptions: {}, gridEvents: {}, formOptions: undefined, showSearchForm: true, }; } export class VxeGridApi = any> { public formApi = {} as ExtendedFormApi; // private prevState: null | VxeGridProps = null; public grid = {} as VxeGridInstance; public state: null | VxeGridProps = null; public store: Store>; private isMounted = false; private stateHandler: StateHandler; constructor(options: VxeGridProps = {}) { const storeState = { ...options }; const defaultState = getDefaultState(); this.store = new Store( mergeWithArrayOverride(storeState, defaultState), { onUpdate: () => { // this.prevState = this.state; this.state = this.store.state; }, }, ); this.state = this.store.state; this.stateHandler = new StateHandler(); bindMethods(this); } mount(instance: null | VxeGridInstance, formApi: ExtendedFormApi) { if (!this.isMounted && instance) { this.grid = instance; this.formApi = formApi; this.stateHandler.setConditionTrue(); this.isMounted = true; } } async query(params: Record = {}) { try { await this.grid.commitProxy('query', toRaw(params)); } catch (error) { console.error('Error occurred while querying:', error); } } async reload(params: Record = {}) { try { await this.grid.commitProxy('reload', toRaw(params)); } catch (error) { console.error('Error occurred while reloading:', error); } } setGridOptions(options: Partial) { this.setState({ gridOptions: options, }); } setLoading(isLoading: boolean) { this.setState({ gridOptions: { loading: isLoading, }, }); } setState( stateOrFn: | ((prev: VxeGridProps) => Partial>) | Partial>, ) { if (isFunction(stateOrFn)) { this.store.setState((prev) => { return mergeWithArrayOverride(stateOrFn(prev), prev); }); } else { this.store.setState((prev) => mergeWithArrayOverride(stateOrFn, prev)); } } toggleSearchForm(show?: boolean) { this.setState({ showSearchForm: isBoolean(show) ? show : !this.state?.showSearchForm, }); // nextTick(() => { // this.grid.recalculate(); // }); return this.state?.showSearchForm; } unmount() { this.isMounted = false; this.stateHandler.reset(); } } ================================================ FILE: packages/effects/plugins/src/vxe-table/extends.ts ================================================ import type { Recordable } from '@vben/types'; import type { VxeGridProps, VxeUIExport } from 'vxe-table'; import type { VxeGridApi } from './api'; import { formatDate, formatDateTime, isFunction } from '@vben/utils'; export function extendProxyOptions( api: VxeGridApi, options: VxeGridProps, getFormValues: () => Recordable, ) { [ 'query', 'querySuccess', 'queryError', 'queryAll', 'queryAllSuccess', 'queryAllError', ].forEach((key) => { extendProxyOption(key, api, options, getFormValues); }); } function extendProxyOption( key: string, api: VxeGridApi, options: VxeGridProps, getFormValues: () => Recordable, ) { const { proxyConfig } = options; const configFn = (proxyConfig?.ajax as Recordable)?.[key]; if (!isFunction(configFn)) { return options; } const wrapperFn = async ( params: Recordable, customValues: Recordable, ...args: Recordable[] ) => { const formValues = getFormValues(); const data = await configFn( params, { /** * 开启toolbarConfig.refresh功能 * 点击刷新按钮 这里的值为PointerEvent 会携带错误参数 */ ...(customValues instanceof PointerEvent ? {} : customValues), ...formValues, }, ...args, ); return data; }; api.setState({ gridOptions: { proxyConfig: { ajax: { [key]: wrapperFn, }, }, }, }); } export function extendsDefaultFormatter(vxeUI: VxeUIExport) { vxeUI.formats.add('formatDate', { tableCellFormatMethod({ cellValue }) { return formatDate(cellValue); }, }); vxeUI.formats.add('formatDateTime', { tableCellFormatMethod({ cellValue }) { return formatDateTime(cellValue); }, }); } ================================================ FILE: packages/effects/plugins/src/vxe-table/index.ts ================================================ export { setupVbenVxeTable } from './init'; export type { VxeTableGridOptions } from './types'; export * from './use-vxe-grid'; export { default as VbenVxeGrid } from './use-vxe-grid.vue'; export type { VxeGridDefines } from 'vxe-table'; export type { VxeGridListeners, VxeGridProps, VxeGridPropTypes, } from 'vxe-table'; ================================================ FILE: packages/effects/plugins/src/vxe-table/init.ts ================================================ import type { SetupVxeTable } from './types'; import { defineComponent, watch } from 'vue'; import { usePreferences } from '@vben/preferences'; import { useVbenForm } from '@vben-core/form-ui'; /** * 该插件提供了在表格中渲染第三方组件,用于兼容 ant-design-vue 组件库 * * 解决组件问题(如select浮层与失焦冲突) * * @see https://vxeui.com/other4/#/plugin-render-antd/start/full/npmInstall * @see https://vxetable.cn/other4/#/table/other/antd */ import VxeUIPluginRenderAntd from '@vxe-ui/plugin-render-antd'; import { VxeButton, VxeCheckbox, // VxeFormGather, // VxeForm, // VxeFormItem, VxeIcon, VxeInput, VxeLoading, VxeModal, VxeNumberInput, VxePager, // VxeList, // VxeModal, // VxeOptgroup, // VxeOption, // VxePulldown, // VxeRadio, // VxeRadioButton, VxeRadioGroup, VxeSelect, VxeTooltip, VxeUI, VxeUpload, // VxeSwitch, // VxeTextarea, } from 'vxe-pc-ui'; import enUS from 'vxe-pc-ui/lib/language/en-US'; // 导入默认的语言 import zhCN from 'vxe-pc-ui/lib/language/zh-CN'; import { VxeColgroup, VxeColumn, VxeGrid, VxeTable, VxeToolbar, } from 'vxe-table'; import { extendsDefaultFormatter } from './extends'; import '@vxe-ui/plugin-render-antd/dist/style.css'; // 是否加载过 let isInit = false; // eslint-disable-next-line import/no-mutable-exports export let useTableForm: typeof useVbenForm; // 部分组件,如果没注册,vxe-table 会报错,这里实际没用组件,只是为了不报错,同时可以减少打包体积 const createVirtualComponent = (name = '') => { return defineComponent({ name, }); }; export function initVxeTable() { if (isInit) { return; } VxeUI.component(VxeTable); VxeUI.component(VxeColumn); VxeUI.component(VxeColgroup); VxeUI.component(VxeGrid); VxeUI.component(VxeToolbar); VxeUI.component(VxeButton); // VxeUI.component(VxeButtonGroup); VxeUI.component(VxeCheckbox); // VxeUI.component(VxeCheckboxGroup); VxeUI.component(createVirtualComponent('VxeForm')); // VxeUI.component(VxeFormGather); // VxeUI.component(VxeFormItem); VxeUI.component(VxeIcon); VxeUI.component(VxeInput); // VxeUI.component(VxeList); VxeUI.component(VxeLoading); VxeUI.component(VxeModal); VxeUI.component(VxeNumberInput); // VxeUI.component(VxeOptgroup); // VxeUI.component(VxeOption); VxeUI.component(VxePager); // VxeUI.component(VxePulldown); // VxeUI.component(VxeRadio); // VxeUI.component(VxeRadioButton); VxeUI.component(VxeRadioGroup); VxeUI.component(VxeSelect); // VxeUI.component(VxeSwitch); // VxeUI.component(VxeTextarea); VxeUI.component(VxeTooltip); VxeUI.component(VxeUpload); VxeUI.use(VxeUIPluginRenderAntd); isInit = true; } export function setupVbenVxeTable(setupOptions: SetupVxeTable) { const { configVxeTable, useVbenForm } = setupOptions; initVxeTable(); useTableForm = useVbenForm; const { isDark, locale } = usePreferences(); const localMap = { 'zh-CN': zhCN, 'en-US': enUS, }; watch( [() => isDark.value, () => locale.value], ([isDarkValue, localeValue]) => { VxeUI.setTheme(isDarkValue ? 'dark' : 'light'); VxeUI.setI18n(localeValue, localMap[localeValue]); VxeUI.setLanguage(localeValue); }, { immediate: true, }, ); extendsDefaultFormatter(VxeUI); configVxeTable(VxeUI); } ================================================ FILE: packages/effects/plugins/src/vxe-table/style.css ================================================ :root .vxe-grid { --vxe-ui-font-color: hsl(var(--foreground)); --vxe-ui-font-primary-color: hsl(var(--primary)); /* --vxe-ui-font-lighten-color: #babdc0; --vxe-ui-font-darken-color: #86898e; */ --vxe-ui-font-disabled-color: hsl(var(--foreground) / 50%); /* base */ --vxe-ui-base-popup-border-color: hsl(var(--border)); --vxe-ui-input-disabled-color: hsl(var(--border) / 60%); /* --vxe-ui-base-popup-box-shadow: 0px 12px 30px 8px rgb(0 0 0 / 50%); */ /* layout */ --vxe-ui-layout-background-color: hsl(var(--background)); --vxe-ui-table-resizable-line-color: hsl(var(--heavy)); /* --vxe-ui-table-fixed-left-scrolling-box-shadow: 8px 0px 10px -5px hsl(var(--accent)); --vxe-ui-table-fixed-right-scrolling-box-shadow: -8px 0px 10px -5px hsl(var(--accent)); */ /* input */ --vxe-ui-input-border-color: hsl(var(--border)); /* --vxe-ui-input-placeholder-color: #8d9095; */ /* --vxe-ui-input-disabled-background-color: #262727; */ /* loading */ --vxe-ui-loading-background-color: hsl(var(--overlay-content)); /* table */ --vxe-ui-table-header-background-color: hsl(0deg 0% 98%); --vxe-ui-table-border-color: hsl(var(--border)); --vxe-ui-table-row-hover-background-color: hsl(var(--accent-hover)); --vxe-ui-table-row-striped-background-color: hsl(var(--accent) / 60%); --vxe-ui-table-row-hover-striped-background-color: hsl(var(--accent)); --vxe-ui-table-row-radio-checked-background-color: hsl(var(--accent)); --vxe-ui-table-row-hover-radio-checked-background-color: hsl( var(--accent-hover) ); --vxe-ui-table-row-checkbox-checked-background-color: hsl(var(--accent)); --vxe-ui-table-row-hover-checkbox-checked-background-color: hsl( var(--accent-hover) ); --vxe-ui-table-row-current-background-color: hsl(var(--accent)); --vxe-ui-table-row-hover-current-background-color: hsl(var(--accent-hover)); --vxe-ui-font-primary-tinge-color: hsl(var(--primary)); --vxe-ui-font-primary-lighten-color: hsl(var(--primary) / 60%); --vxe-ui-font-primary-darken-color: hsl(var(--primary)); /* 拖拽列宽颜色 */ --vxe-ui-table-resizable-drag-line-color: hsl(var(--primary)); /* --vxe-ui-table-fixed-scrolling-box-shadow-color: rgb(0 0 0 / 80%); */ height: auto !important; } html[data-vxe-ui-theme='dark'] .vxe-grid { --vxe-ui-table-header-background-color: hsl(var(--accent) / 50%); } .vxe-pager { .vxe-pager--prev-btn:not(.is--disabled):active, .vxe-pager--next-btn:not(.is--disabled):active, .vxe-pager--num-btn:not(.is--disabled):active, .vxe-pager--jump-prev:not(.is--disabled):active, .vxe-pager--jump-next:not(.is--disabled):active, .vxe-pager--prev-btn:not(.is--disabled):focus, .vxe-pager--next-btn:not(.is--disabled):focus, .vxe-pager--num-btn:not(.is--disabled):focus, .vxe-pager--jump-prev:not(.is--disabled):focus, .vxe-pager--jump-next:not(.is--disabled):focus { color: hsl(var(--accent-foreground)); background-color: hsl(var(--accent)); border: 1px solid hsl(var(--border)); box-shadow: 0 0 0 1px hsl(var(--border)); } .vxe-pager--wrapper { display: flex; align-items: center; } .vxe-pager--sizes { margin-right: auto; } } .vxe-pager--wrapper { @apply justify-center md:justify-end; } .vxe-tools--operate { margin-right: 0.25rem; margin-left: 0.75rem; } .vxe-table-custom--checkbox-option:hover { background: none !important; } .vxe-toolbar { padding: 0; } /** 关闭搜索表单 */ .vxe-grid:not(.vxe-grid--form-wrapper form) .vxe-toolbar { padding: 0.6em 0 !important; } /** 开启搜索表单 */ .vxe-grid:has(.vxe-grid--form-wrapper form) .vxe-toolbar { padding: 0 0 0.6em !important; } .vxe-tools--operate:not(:has(button)) { margin-left: 0; } .vxe-grid--layout-header-wrapper { overflow: visible; } .vxe-grid--layout-body-content-wrapper { overflow: hidden; } /* modal/drawer中使用 tooltip需要设置更高的z-index 防止被遮挡 */ body:has(div[data-dismissable-layer]) .vxe-tooltip--wrapper { /* tooltip直接在style中设置 需要!important来实现优先级覆盖 */ z-index: calc(var(--popup-z-index) + 1) !important; } /* 在border: inner场景下 去除下边的边框 TODO: 最后一条数据hover/check仍会显示边框 */ .vxe-table.border--inner .vxe-table--border-line { border-radius: var(--vxe-ui-table-border-radius) var(--vxe-ui-table-border-radius) 0 0; } /* modal/drawer里使用列配置 重置列弹窗被遮挡 */ .vxe-dynamics--modal > .vxe-modal--wrapper { z-index: calc(var(--popup-z-index) + 1) !important; } ================================================ FILE: packages/effects/plugins/src/vxe-table/types.ts ================================================ import type { VxeGridListeners, VxeGridPropTypes, VxeGridProps as VxeTableGridProps, VxeUIExport, } from 'vxe-table'; import type { Ref } from 'vue'; import type { ClassType, DeepPartial } from '@vben/types'; import type { BaseFormComponentType, VbenFormProps } from '@vben-core/form-ui'; import type { VxeGridApi } from './api'; import { useVbenForm } from '@vben-core/form-ui'; export interface VxePaginationInfo { currentPage: number; pageSize: number; total: number; } interface ToolbarConfigOptions extends VxeGridPropTypes.ToolbarConfig { /** 是否显示切换搜索表单的按钮 */ search?: boolean; } export interface VxeTableGridOptions extends VxeTableGridProps { /** 工具栏配置 */ toolbarConfig?: ToolbarConfigOptions; } export interface SeparatorOptions { show?: boolean; backgroundColor?: string; } export interface VxeGridProps< T extends Record = any, D extends BaseFormComponentType = BaseFormComponentType, > { /** * 标题 */ tableTitle?: string; /** * 标题帮助 */ tableTitleHelp?: string; /** * 组件class */ class?: ClassType; /** * vxe-grid class */ gridClass?: ClassType; /** * vxe-grid 配置 */ gridOptions?: DeepPartial>; /** * vxe-grid 事件 */ gridEvents?: Partial>; /** * 表单配置 */ formOptions?: VbenFormProps; /** * 显示搜索表单 */ showSearchForm?: boolean; /** * 搜索表单与表格主体之间的分隔条 */ separator?: boolean | SeparatorOptions; } export type ExtendedVxeGridApi< D extends Record = any, F extends BaseFormComponentType = BaseFormComponentType, > = VxeGridApi & { useStore: >>( selector?: (state: NoInfer>) => T, ) => Readonly>; }; export interface SetupVxeTable { configVxeTable: (ui: VxeUIExport) => void; useVbenForm: typeof useVbenForm; } ================================================ FILE: packages/effects/plugins/src/vxe-table/use-vxe-grid.ts ================================================ import type { VxeGridSlots, VxeGridSlotTypes } from 'vxe-table'; import type { SlotsType } from 'vue'; import type { BaseFormComponentType } from '@vben-core/form-ui'; import type { ExtendedVxeGridApi, VxeGridProps } from './types'; import { defineComponent, h, onBeforeUnmount } from 'vue'; import { useStore } from '@vben-core/shared/store'; import { VxeGridApi } from './api'; import VxeGrid from './use-vxe-grid.vue'; type FilteredSlots = { [K in keyof VxeGridSlots as K extends 'form' ? never : K]: VxeGridSlots[K]; }; export function useVbenVxeGrid< T extends Record = any, D extends BaseFormComponentType = BaseFormComponentType, >(options: VxeGridProps) { // const IS_REACTIVE = isReactive(options); const api = new VxeGridApi(options); const extendedApi: ExtendedVxeGridApi = api as ExtendedVxeGridApi; extendedApi.useStore = (selector) => { return useStore(api.store, selector); }; const Grid = defineComponent( (props: VxeGridProps, { attrs, slots }) => { onBeforeUnmount(() => { api.unmount(); }); api.setState({ ...props, ...attrs }); return () => h(VxeGrid, { ...props, ...attrs, api: extendedApi }, slots); }, { name: 'VbenVxeGrid', inheritAttrs: false, slots: Object as SlotsType< { // 表格标题 'table-title': undefined; // 工具栏左侧部分 'toolbar-actions': VxeGridSlotTypes.DefaultSlotParams; // 工具栏右侧部分 'toolbar-tools': VxeGridSlotTypes.DefaultSlotParams; } & FilteredSlots >, }, ); // Add reactivity support // if (IS_REACTIVE) { // watch( // () => options, // () => { // api.setState(options); // }, // { immediate: true }, // ); // } return [Grid, extendedApi] as const; } export type UseVbenVxeGrid = typeof useVbenVxeGrid; ================================================ FILE: packages/effects/plugins/src/vxe-table/use-vxe-grid.vue ================================================ ================================================ FILE: packages/effects/plugins/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/effects/request/package.json ================================================ { "name": "@vben/request", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/effects/request" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben/locales": "workspace:*", "@vben/utils": "workspace:*", "axios": "catalog:", "qs": "catalog:" }, "devDependencies": { "@types/qs": "catalog:", "axios-mock-adapter": "catalog:" } } ================================================ FILE: packages/effects/request/src/index.ts ================================================ export * from './request-client'; export * from 'axios'; export { stringify } from 'qs'; ================================================ FILE: packages/effects/request/src/request-client/index.ts ================================================ export * from './preset-interceptors'; export * from './request-client'; export type * from './types'; ================================================ FILE: packages/effects/request/src/request-client/modules/downloader.test.ts ================================================ import type { AxiosRequestConfig } from 'axios'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { FileDownloader } from './downloader'; describe('fileDownloader', () => { let fileDownloader: FileDownloader; const mockAxiosInstance = { get: vi.fn(), } as any; beforeEach(() => { fileDownloader = new FileDownloader(mockAxiosInstance); }); it('should create an instance of FileDownloader', () => { expect(fileDownloader).toBeInstanceOf(FileDownloader); }); it('should download a file and return a Blob', async () => { const url = 'https://example.com/file'; const mockBlob = new Blob(['file content'], { type: 'text/plain' }); const mockResponse: Blob = mockBlob; mockAxiosInstance.get.mockResolvedValueOnce(mockResponse); const result = await fileDownloader.download(url); expect(result).toBeInstanceOf(Blob); expect(result).toEqual(mockBlob); expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, { responseType: 'blob', responseReturn: 'body', }); }); it('should merge provided config with default config', async () => { const url = 'https://example.com/file'; const mockBlob = new Blob(['file content'], { type: 'text/plain' }); const mockResponse: Blob = mockBlob; mockAxiosInstance.get.mockResolvedValueOnce(mockResponse); const customConfig: AxiosRequestConfig = { headers: { 'Custom-Header': 'value' }, }; const result = await fileDownloader.download(url, customConfig); expect(result).toBeInstanceOf(Blob); expect(result).toEqual(mockBlob); expect(mockAxiosInstance.get).toHaveBeenCalledWith(url, { ...customConfig, responseType: 'blob', responseReturn: 'body', }); }); it('should handle errors gracefully', async () => { const url = 'https://example.com/file'; mockAxiosInstance.get.mockRejectedValueOnce(new Error('Network Error')); await expect(fileDownloader.download(url)).rejects.toThrow('Network Error'); }); it('should handle empty URL gracefully', async () => { const url = ''; mockAxiosInstance.get.mockRejectedValueOnce( new Error('Request failed with status code 404'), ); await expect(fileDownloader.download(url)).rejects.toThrow( 'Request failed with status code 404', ); }); it('should handle null URL gracefully', async () => { const url = null as unknown as string; mockAxiosInstance.get.mockRejectedValueOnce( new Error('Request failed with status code 404'), ); await expect(fileDownloader.download(url)).rejects.toThrow( 'Request failed with status code 404', ); }); }); ================================================ FILE: packages/effects/request/src/request-client/modules/downloader.ts ================================================ import type { RequestClient } from '../request-client'; import type { RequestClientConfig } from '../types'; type DownloadRequestConfig = { /** * 定义期望获得的数据类型。 * raw: 原始的AxiosResponse,包括headers、status等。 * body: 只返回响应数据的BODY部分(Blob) */ responseReturn?: 'body' | 'raw'; } & Omit; class FileDownloader { private client: RequestClient; constructor(client: RequestClient) { this.client = client; } /** * 下载文件 * @param url 文件的完整链接 * @param config 配置信息,可选。 * @returns 如果config.responseReturn为'body',则返回Blob(默认),否则返回RequestResponse */ public async download( url: string, config?: DownloadRequestConfig, ): Promise { const finalConfig: DownloadRequestConfig = { responseReturn: 'body', ...config, responseType: 'blob', }; const response = await this.client.get(url, finalConfig); return response; } } export { FileDownloader }; ================================================ FILE: packages/effects/request/src/request-client/modules/interceptor.ts ================================================ import type { AxiosInstance, AxiosResponse } from 'axios'; import type { RequestInterceptorConfig, ResponseInterceptorConfig, } from '../types'; const defaultRequestInterceptorConfig: RequestInterceptorConfig = { fulfilled: (response) => response, rejected: (error) => Promise.reject(error), }; const defaultResponseInterceptorConfig: ResponseInterceptorConfig = { fulfilled: (response: AxiosResponse) => response, rejected: (error) => Promise.reject(error), }; class InterceptorManager { private axiosInstance: AxiosInstance; constructor(instance: AxiosInstance) { this.axiosInstance = instance; } addRequestInterceptor({ fulfilled, rejected, }: RequestInterceptorConfig = defaultRequestInterceptorConfig) { this.axiosInstance.interceptors.request.use(fulfilled, rejected); } addResponseInterceptor({ fulfilled, rejected, }: ResponseInterceptorConfig = defaultResponseInterceptorConfig) { this.axiosInstance.interceptors.response.use(fulfilled, rejected); } } export { InterceptorManager }; ================================================ FILE: packages/effects/request/src/request-client/modules/sse.test.ts ================================================ import type { RequestClient } from '../request-client'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { SSE } from './sse'; // 模拟 TextDecoder const OriginalTextDecoder = globalThis.TextDecoder; beforeEach(() => { vi.stubGlobal( 'TextDecoder', class { private decoder = new OriginalTextDecoder(); decode(value: Uint8Array, opts?: any) { return this.decoder.decode(value, opts); } }, ); }); // 创建 fetch mock const createFetchMock = (chunks: string[], ok = true) => { const encoder = new TextEncoder(); let index = 0; return vi.fn().mockResolvedValue({ ok, status: ok ? 200 : 500, body: { getReader: () => ({ read: async () => { if (index < chunks.length) { return { done: false, value: encoder.encode(chunks[index++]) }; } return { done: true, value: undefined }; }, }), }, }); }; describe('sSE', () => { let client: RequestClient; let sse: SSE; beforeEach(() => { vi.restoreAllMocks(); client = { getBaseUrl: () => 'http://localhost', instance: { interceptors: { request: { handlers: [], }, }, }, } as unknown as RequestClient; sse = new SSE(client); }); it('should call requestSSE when postSSE is used', async () => { const spy = vi.spyOn(sse, 'requestSSE').mockResolvedValue(undefined); await sse.postSSE('/test', { foo: 'bar' }, { headers: { a: '1' } }); expect(spy).toHaveBeenCalledWith( '/test', { foo: 'bar' }, { headers: { a: '1' }, method: 'POST', }, ); }); it('should throw error if fetch response not ok', async () => { vi.stubGlobal('fetch', createFetchMock([], false)); await expect(sse.requestSSE('/bad')).rejects.toThrow( 'HTTP error! status: 500', ); }); it('should trigger onMessage and onEnd callbacks', async () => { const messages: string[] = []; const onMessage = vi.fn((msg: string) => messages.push(msg)); const onEnd = vi.fn(); vi.stubGlobal('fetch', createFetchMock(['hello', ' world'])); await sse.requestSSE('/sse', undefined, { onMessage, onEnd }); expect(onMessage).toHaveBeenCalledTimes(2); expect(messages.join('')).toBe('hello world'); // onEnd 不再带参数 expect(onEnd).toHaveBeenCalled(); }); it('should apply request interceptors', async () => { const interceptor = vi.fn(async (config) => { config.headers['x-test'] = 'intercepted'; return config; }); (client.instance.interceptors.request as any).handlers.push({ fulfilled: interceptor, }); // 创建 fetch mock,并挂到全局 const fetchMock = createFetchMock(['data']); vi.stubGlobal('fetch', fetchMock); await sse.requestSSE('/sse', undefined, {}); expect(interceptor).toHaveBeenCalled(); expect(fetchMock).toHaveBeenCalledWith( 'http://localhost/sse', expect.objectContaining({ headers: expect.any(Headers), }), ); const calls = fetchMock.mock?.calls; expect(calls).toBeDefined(); expect(calls?.length).toBeGreaterThan(0); const init = calls?.[0]?.[1] as RequestInit; expect(init).toBeDefined(); const headers = init?.headers as Headers; expect(headers?.get('x-test')).toBe('intercepted'); expect(headers?.get('accept')).toBe('text/event-stream'); }); it('should throw error when no reader', async () => { vi.stubGlobal( 'fetch', vi.fn().mockResolvedValue({ ok: true, status: 200, body: null, }), ); await expect(sse.requestSSE('/sse')).rejects.toThrow('No reader'); }); }); ================================================ FILE: packages/effects/request/src/request-client/modules/sse.ts ================================================ import type { AxiosRequestHeaders, InternalAxiosRequestConfig } from 'axios'; import type { RequestClient } from '../request-client'; import type { SseRequestOptions } from '../types'; /** * SSE模块 */ class SSE { private client: RequestClient; constructor(client: RequestClient) { this.client = client; } public async postSSE( url: string, data?: any, requestOptions?: SseRequestOptions, ) { return this.requestSSE(url, data, { ...requestOptions, method: 'POST', }); } /** * SSE请求方法 * @param url - 请求URL * @param data - 请求数据 * @param requestOptions - SSE请求选项 */ public async requestSSE( url: string, data?: any, requestOptions?: SseRequestOptions, ) { const baseUrl = this.client.getBaseUrl() || ''; let axiosConfig: InternalAxiosRequestConfig = { url, method: (requestOptions?.method as any) ?? 'GET', headers: {} as AxiosRequestHeaders, }; const requestInterceptors = this.client.instance.interceptors .request as any; if ( requestInterceptors.handlers && requestInterceptors.handlers.length > 0 ) { for (const handler of requestInterceptors.handlers) { if (typeof handler?.fulfilled === 'function') { const next = await handler.fulfilled(axiosConfig as any); if (next) axiosConfig = next as InternalAxiosRequestConfig; } } } const merged = new Headers(); Object.entries( (axiosConfig.headers ?? {}) as Record, ).forEach(([k, v]) => merged.set(k, String(v))); if (requestOptions?.headers) { new Headers(requestOptions.headers).forEach((v, k) => merged.set(k, v)); } if (!merged.has('accept')) { merged.set('accept', 'text/event-stream'); } let bodyInit = requestOptions?.body ?? data; const ct = (merged.get('content-type') || '').toLowerCase(); if ( bodyInit && typeof bodyInit === 'object' && !ArrayBuffer.isView(bodyInit as any) && !(bodyInit instanceof ArrayBuffer) && !(bodyInit instanceof Blob) && !(bodyInit instanceof FormData) && ct.includes('application/json') ) { bodyInit = JSON.stringify(bodyInit); } const requestInit: RequestInit = { ...requestOptions, method: axiosConfig.method, headers: merged, body: bodyInit, }; const response = await fetch(safeJoinUrl(baseUrl, url), requestInit); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body?.getReader(); const decoder = new TextDecoder(); if (!reader) { throw new Error('No reader'); } let isEnd = false; while (!isEnd) { const { done, value } = await reader.read(); if (done) { isEnd = true; decoder.decode(new Uint8Array(0), { stream: false }); requestOptions?.onEnd?.(); reader.releaseLock?.(); break; } const content = decoder.decode(value, { stream: true }); requestOptions?.onMessage?.(content); } } } function safeJoinUrl(baseUrl: string | undefined, url: string): string { if (!baseUrl) { return url; // 没有 baseUrl,直接返回 url } // 如果 url 本身就是绝对地址,直接返回 if (/^https?:\/\//i.test(url)) { return url; } // 如果 baseUrl 是完整 URL,就用 new URL if (/^https?:\/\//i.test(baseUrl)) { return new URL(url, baseUrl).toString(); } // 否则,当作路径拼接 return `${baseUrl.replace(/\/+$/, '')}/${url.replace(/^\/+/, '')}`; } export { SSE }; ================================================ FILE: packages/effects/request/src/request-client/modules/uploader.test.ts ================================================ import type { AxiosRequestConfig, AxiosResponse } from 'axios'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { FileUploader } from './uploader'; describe('fileUploader', () => { let fileUploader: FileUploader; // Mock the AxiosInstance const mockAxiosInstance = { post: vi.fn(), } as any; beforeEach(() => { fileUploader = new FileUploader(mockAxiosInstance); }); it('should create an instance of FileUploader', () => { expect(fileUploader).toBeInstanceOf(FileUploader); }); it('should upload a file and return the response', async () => { const url = 'https://example.com/upload'; const file = new File(['file content'], 'test.txt', { type: 'text/plain' }); const mockResponse: AxiosResponse = { config: {} as any, data: { success: true }, headers: {}, status: 200, statusText: 'OK', }; ( mockAxiosInstance.post as unknown as ReturnType ).mockResolvedValueOnce(mockResponse); const result = await fileUploader.upload(url, { file }); expect(result).toEqual(mockResponse); expect(mockAxiosInstance.post).toHaveBeenCalledWith( url, expect.any(FormData), { headers: { 'Content-Type': 'multipart/form-data', }, }, ); }); it('should merge provided config with default config', async () => { const url = 'https://example.com/upload'; const file = new File(['file content'], 'test.txt', { type: 'text/plain' }); const mockResponse: AxiosResponse = { config: {} as any, data: { success: true }, headers: {}, status: 200, statusText: 'OK', }; ( mockAxiosInstance.post as unknown as ReturnType ).mockResolvedValueOnce(mockResponse); const customConfig: AxiosRequestConfig = { headers: { 'Custom-Header': 'value' }, }; const result = await fileUploader.upload(url, { file }, customConfig); expect(result).toEqual(mockResponse); expect(mockAxiosInstance.post).toHaveBeenCalledWith( url, expect.any(FormData), { headers: { 'Content-Type': 'multipart/form-data', 'Custom-Header': 'value', }, }, ); }); it('should handle errors gracefully', async () => { const url = 'https://example.com/upload'; const file = new File(['file content'], 'test.txt', { type: 'text/plain' }); ( mockAxiosInstance.post as unknown as ReturnType ).mockRejectedValueOnce(new Error('Network Error')); await expect(fileUploader.upload(url, { file })).rejects.toThrow( 'Network Error', ); }); it('should handle empty URL gracefully', async () => { const url = ''; const file = new File(['file content'], 'test.txt', { type: 'text/plain' }); ( mockAxiosInstance.post as unknown as ReturnType ).mockRejectedValueOnce(new Error('Request failed with status code 404')); await expect(fileUploader.upload(url, { file })).rejects.toThrow( 'Request failed with status code 404', ); }); it('should handle null URL gracefully', async () => { const url = null as unknown as string; const file = new File(['file content'], 'test.txt', { type: 'text/plain' }); ( mockAxiosInstance.post as unknown as ReturnType ).mockRejectedValueOnce(new Error('Request failed with status code 404')); await expect(fileUploader.upload(url, { file })).rejects.toThrow( 'Request failed with status code 404', ); }); }); ================================================ FILE: packages/effects/request/src/request-client/modules/uploader.ts ================================================ import type { RequestClient } from '../request-client'; import type { RequestClientConfig } from '../types'; import { isUndefined } from '@vben/utils'; class FileUploader { private client: RequestClient; constructor(client: RequestClient) { this.client = client; } public async upload( url: string, data: Record & { file: Blob | File }, config?: RequestClientConfig, ): Promise { const formData = new FormData(); Object.entries(data).forEach(([key, value]) => { if (Array.isArray(value)) { value.forEach((item, index) => { !isUndefined(item) && formData.append(`${key}[${index}]`, item); }); } else { !isUndefined(value) && formData.append(key, value); } }); const finalConfig: RequestClientConfig = { ...config, headers: { 'Content-Type': 'multipart/form-data', ...config?.headers, }, }; return this.client.post(url, formData, finalConfig); } } export { FileUploader }; ================================================ FILE: packages/effects/request/src/request-client/preset-interceptors.ts ================================================ import type { RequestClient } from './request-client'; import type { MakeErrorMessageFn, ResponseInterceptorConfig } from './types'; import { $t } from '@vben/locales'; import { isFunction } from '@vben/utils'; import axios from 'axios'; export const defaultResponseInterceptor = ({ codeField = 'code', dataField = 'data', successCode = 0, }: { /** 响应数据中代表访问结果的字段名 */ codeField: string; /** 响应数据中装载实际数据的字段名,或者提供一个函数从响应数据中解析需要返回的数据 */ dataField: ((response: any) => any) | string; /** 当codeField所指定的字段值与successCode相同时,代表接口访问成功。如果提供一个函数,则返回true代表接口访问成功 */ successCode: ((code: any) => boolean) | number | string; }): ResponseInterceptorConfig => { return { fulfilled: (response) => { const { config, data: responseData, status } = response; if (config.responseReturn === 'raw') { return response; } if (status >= 200 && status < 400) { if (config.responseReturn === 'body') { return responseData; } else if ( isFunction(successCode) ? successCode(responseData[codeField]) : responseData[codeField] === successCode ) { return isFunction(dataField) ? dataField(responseData) : responseData[dataField]; } } throw Object.assign({}, response, { response }); }, }; }; export const authenticateResponseInterceptor = ({ client, doReAuthenticate, doRefreshToken, enableRefreshToken, formatToken, }: { client: RequestClient; doReAuthenticate: () => Promise; doRefreshToken: () => Promise; enableRefreshToken: boolean; formatToken: (token: string) => null | string; }): ResponseInterceptorConfig => { return { rejected: async (error) => { const { config, response } = error; // 如果不是 401 错误,直接抛出异常 if (response?.status !== 401) { throw error; } // 判断是否启用了 refreshToken 功能 // 如果没有启用或者已经是重试请求了,直接跳转到重新登录 if (!enableRefreshToken || config.__isRetryRequest) { await doReAuthenticate(); throw error; } // 如果正在刷新 token,则将请求加入队列,等待刷新完成 if (client.isRefreshing) { return new Promise((resolve) => { client.refreshTokenQueue.push((newToken: string) => { config.headers.Authorization = formatToken(newToken); resolve(client.request(config.url, { ...config })); }); }); } // 标记开始刷新 token client.isRefreshing = true; // 标记当前请求为重试请求,避免无限循环 config.__isRetryRequest = true; try { const newToken = await doRefreshToken(); // 处理队列中的请求 client.refreshTokenQueue.forEach((callback) => callback(newToken)); // 清空队列 client.refreshTokenQueue = []; return client.request(error.config.url, { ...error.config }); } catch (refreshError) { // 如果刷新 token 失败,处理错误(如强制登出或跳转登录页面) client.refreshTokenQueue.forEach((callback) => callback('')); client.refreshTokenQueue = []; console.error('Refresh token failed, please login again.'); await doReAuthenticate(); throw refreshError; } finally { client.isRefreshing = false; } }, }; }; export const errorMessageResponseInterceptor = ( makeErrorMessage?: MakeErrorMessageFn, ): ResponseInterceptorConfig => { return { rejected: (error: any) => { if (axios.isCancel(error)) { return Promise.reject(error); } const err: string = error?.toString?.() ?? ''; let errMsg = ''; if (err?.includes('Network Error')) { errMsg = $t('ui.fallback.http.networkError'); } else if (error?.message?.includes?.('timeout')) { errMsg = $t('ui.fallback.http.requestTimeout'); } if (errMsg) { makeErrorMessage?.(errMsg, error); return Promise.reject(error); } let errorMessage = ''; const status = error?.response?.status; switch (status) { case 400: { errorMessage = $t('ui.fallback.http.badRequest'); break; } case 401: { errorMessage = $t('ui.fallback.http.unauthorized'); break; } case 403: { errorMessage = $t('ui.fallback.http.forbidden'); break; } case 404: { errorMessage = $t('ui.fallback.http.notFound'); break; } case 408: { errorMessage = $t('ui.fallback.http.requestTimeout'); break; } default: { errorMessage = $t('ui.fallback.http.internalServerError'); } } makeErrorMessage?.(errorMessage, error); return Promise.reject(error); }, }; }; ================================================ FILE: packages/effects/request/src/request-client/request-client.test.ts ================================================ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { RequestClient } from './request-client'; describe('requestClient', () => { let mock: MockAdapter; let requestClient: RequestClient; beforeEach(() => { mock = new MockAdapter(axios); requestClient = new RequestClient(); }); afterEach(() => { mock.reset(); }); it('should successfully make a GET request', async () => { mock.onGet('test/url').reply(200, { data: 'response' }); const response = await requestClient.get('test/url'); expect(response.data).toEqual({ data: 'response' }); }); it('should successfully make a POST request', async () => { const postData = { key: 'value' }; const mockData = { data: 'response' }; mock.onPost('/test/post', postData).reply(200, mockData); const response = await requestClient.post('/test/post', postData); expect(response.data).toEqual(mockData); }); it('should successfully make a PUT request', async () => { const putData = { key: 'updatedValue' }; const mockData = { data: 'updated response' }; mock.onPut('/test/put', putData).reply(200, mockData); const response = await requestClient.put('/test/put', putData); expect(response.data).toEqual(mockData); }); it('should successfully make a DELETE request', async () => { const mockData = { data: 'delete response' }; mock.onDelete('/test/delete').reply(200, mockData); const response = await requestClient.delete('/test/delete'); expect(response.data).toEqual(mockData); }); it('should handle network errors', async () => { mock.onGet('/test/error').networkError(); try { await requestClient.get('/test/error'); expect(true).toBe(false); } catch (error: any) { expect(error.isAxiosError).toBe(true); expect(error.message).toBe('Network Error'); } }); it('should handle timeout', async () => { mock.onGet('/test/timeout').timeout(); try { await requestClient.get('/test/timeout'); expect(true).toBe(false); } catch (error: any) { expect(error.isAxiosError).toBe(true); expect(error.code).toBe('ECONNABORTED'); } }); it('should successfully upload a file', async () => { const fileData = new Blob(['file contents'], { type: 'text/plain' }); mock.onPost('/test/upload').reply((config) => { return config.data instanceof FormData && config.data.has('file') ? [200, { data: 'file uploaded' }] : [400, { error: 'Bad Request' }]; }); const response = await requestClient.upload('/test/upload', { file: fileData, }); expect(response.data).toEqual({ data: 'file uploaded' }); }); it('should successfully download a file as a blob', async () => { const mockFileContent = new Blob(['mock file content'], { type: 'text/plain', }); mock.onGet('/test/download').reply(200, mockFileContent); const res = await requestClient.download('/test/download'); expect(res.data).toBeInstanceOf(Blob); }); }); ================================================ FILE: packages/effects/request/src/request-client/request-client.ts ================================================ import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import type { RequestClientConfig, RequestClientOptions } from './types'; import { bindMethods, isString, merge } from '@vben/utils'; import axios from 'axios'; import qs from 'qs'; import { FileDownloader } from './modules/downloader'; import { InterceptorManager } from './modules/interceptor'; import { SSE } from './modules/sse'; import { FileUploader } from './modules/uploader'; function getParamsSerializer( paramsSerializer: RequestClientOptions['paramsSerializer'], ) { if (isString(paramsSerializer)) { switch (paramsSerializer) { case 'brackets': { return (params: any) => qs.stringify(params, { arrayFormat: 'brackets' }); } case 'comma': { return (params: any) => qs.stringify(params, { arrayFormat: 'comma' }); } case 'indices': { return (params: any) => qs.stringify(params, { arrayFormat: 'indices' }); } case 'repeat': { return (params: any) => qs.stringify(params, { arrayFormat: 'repeat' }); } } } return paramsSerializer; } class RequestClient { public addRequestInterceptor: InterceptorManager['addRequestInterceptor']; public addResponseInterceptor: InterceptorManager['addResponseInterceptor']; public download: FileDownloader['download']; public readonly instance: AxiosInstance; // 是否正在刷新token public isRefreshing = false; public postSSE: SSE['postSSE']; // 刷新token队列 public refreshTokenQueue: ((token: string) => void)[] = []; public requestSSE: SSE['requestSSE']; public upload: FileUploader['upload']; /** * 构造函数,用于创建Axios实例 * @param options - Axios请求配置,可选 */ constructor(options: RequestClientOptions = {}) { // 合并默认配置和传入的配置 const defaultConfig: RequestClientOptions = { headers: { 'Content-Type': 'application/json;charset=utf-8', }, responseReturn: 'raw', // 默认超时时间 timeout: 10_000, }; const { ...axiosConfig } = options; const requestConfig = merge(axiosConfig, defaultConfig); requestConfig.paramsSerializer = getParamsSerializer( requestConfig.paramsSerializer, ); this.instance = axios.create(requestConfig); bindMethods(this); // 实例化拦截器管理器 const interceptorManager = new InterceptorManager(this.instance); this.addRequestInterceptor = interceptorManager.addRequestInterceptor.bind(interceptorManager); this.addResponseInterceptor = interceptorManager.addResponseInterceptor.bind(interceptorManager); // 实例化文件上传器 const fileUploader = new FileUploader(this); this.upload = fileUploader.upload.bind(fileUploader); // 实例化文件下载器 const fileDownloader = new FileDownloader(this); this.download = fileDownloader.download.bind(fileDownloader); // 实例化SSE模块 const sse = new SSE(this); this.postSSE = sse.postSSE.bind(sse); this.requestSSE = sse.requestSSE.bind(sse); } /** * DELETE请求方法 */ public delete( url: string, config?: RequestClientConfig, ): Promise { return this.request(url, { ...config, method: 'DELETE' }); } /** * DELETE请求方法 成功会弹出msg */ public deleteWithMsg( url: string, config?: AxiosRequestConfig, ): Promise { return this.request(url, { ...config, method: 'DELETE', successMessageMode: 'message', }); } /** * GET请求方法 */ public get(url: string, config?: RequestClientConfig): Promise { return this.request(url, { ...config, method: 'GET' }); } /** * 获取基础URL */ public getBaseUrl() { return this.instance.defaults.baseURL; } /** * POST请求方法 */ public post( url: string, data?: any, config?: RequestClientConfig, ): Promise { return this.request(url, { ...config, data, method: 'POST' }); } /** * POST请求方法 成功会弹出msg */ public postWithMsg( url: string, data?: any, config?: AxiosRequestConfig, ): Promise { return this.request(url, { ...config, data, method: 'POST', successMessageMode: 'message', }); } /** * PUT请求方法 */ public put( url: string, data?: any, config?: RequestClientConfig, ): Promise { return this.request(url, { ...config, data, method: 'PUT' }); } /** * PUT请求方法 成功会弹出msg */ public putWithMsg( url: string, data?: any, config?: AxiosRequestConfig, ): Promise { return this.request(url, { ...config, data, method: 'PUT', successMessageMode: 'message', }); } /** * 通用的请求方法 */ public async request( url: string, config: RequestClientConfig, ): Promise { try { const response: AxiosResponse = await this.instance({ url, ...config, ...(config.paramsSerializer ? { paramsSerializer: getParamsSerializer(config.paramsSerializer) } : {}), }); return response as T; } catch (error: any) { throw error.response ? error.response.data : error; } } } export { RequestClient }; ================================================ FILE: packages/effects/request/src/request-client/types.ts ================================================ import type { AxiosRequestConfig, AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig, } from 'axios'; type ExtendOptions = { /** * 参数序列化方式。预置的有 * - brackets: ids[]=1&ids[]=2&ids[]=3 * - comma: ids=1,2,3 * - indices: ids[0]=1&ids[1]=2&ids[2]=3 * - repeat: ids=1&ids=2&ids=3 */ paramsSerializer?: | 'brackets' | 'comma' | 'indices' | 'repeat' | AxiosRequestConfig['paramsSerializer']; /** * 响应数据的返回方式。 * - raw: 原始的AxiosResponse,包括headers、status等,不做是否成功请求的检查。 * - body: 返回响应数据的BODY部分(只会根据status检查请求是否成功,忽略对code的判断,这种情况下应由调用方检查请求是否成功)。 * - data: 解构响应的BODY数据,只返回其中的data节点数据(会检查status和code是否为成功状态)。 */ responseReturn?: 'body' | 'data' | 'raw'; }; type RequestClientConfig = AxiosRequestConfig & ExtendOptions; type RequestResponse = AxiosResponse & { config: RequestClientConfig; }; type RequestContentType = | 'application/json;charset=utf-8' | 'application/octet-stream;charset=utf-8' | 'application/x-www-form-urlencoded;charset=utf-8' | 'multipart/form-data;charset=utf-8'; type RequestClientOptions = CreateAxiosDefaults & ExtendOptions; /** * SSE 请求选项 */ interface SseRequestOptions extends RequestInit { onMessage?: (message: string) => void; onEnd?: () => void; } interface RequestInterceptorConfig { fulfilled?: ( config: ExtendOptions & InternalAxiosRequestConfig, ) => | (ExtendOptions & InternalAxiosRequestConfig) | Promise>; rejected?: (error: any) => any; } interface ResponseInterceptorConfig { fulfilled?: ( response: RequestResponse, ) => Promise | RequestResponse; rejected?: (error: any) => any; } type MakeErrorMessageFn = (message: string, error: any) => void; interface HttpResponse { code: number; data: T; msg: string; } export type { HttpResponse, MakeErrorMessageFn, RequestClientConfig, RequestClientOptions, RequestContentType, RequestInterceptorConfig, RequestResponse, ResponseInterceptorConfig, SseRequestOptions, }; export type ErrorMessageMode = 'message' | 'modal' | 'none' | undefined; export type SuccessMessageMode = ErrorMessageMode; /** * 拓展axios的请求配置 */ declare module 'axios' { interface AxiosRequestConfig { /** 是否加密请求参数 */ encrypt?: boolean; /** * 错误弹窗类型 */ errorMessageMode?: ErrorMessageMode; /** * 是否返回原生axios响应 */ isReturnNativeResponse?: boolean; /** * 是否需要转换响应 即只获取{code, msg, data}中的data */ isTransformResponse?: boolean; /** * 成功弹窗类型 */ successMessageMode?: SuccessMessageMode; } } ================================================ FILE: packages/effects/request/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/icons/README.md ================================================ # @vben/icons 用于多个 `app` 公用的图标文件,继承了 `@vben-core/icons` 的所有能力。业务上有通用图标可以放在这里。 ## 用法 ### 添加依赖 ```bash # 进入目标应用目录,例如 apps/xxxx-app # cd apps/xxxx-app pnpm add @vben/icons ``` ### 使用 ```ts import { X } from '@vben/icons'; ``` ================================================ FILE: packages/icons/package.json ================================================ { "name": "@vben/icons", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/icons" }, "license": "MIT", "type": "module", "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben-core/icons": "workspace:*", "@vben-core/shadcn-ui": "workspace:^" }, "devDependencies": { "@iconify/icons-akar-icons": "^1.2.19", "@iconify/icons-ant-design": "^1.2.7", "@iconify/icons-arcticons": "^1.2.77", "@iconify/icons-bi": "^1.2.19", "@iconify/icons-bx": "^1.2.6", "@iconify/icons-carbon": "^1.2.20", "@iconify/icons-devicon": "^1.2.17", "@iconify/icons-emojione": "^1.2.6", "@iconify/icons-eos-icons": "^1.2.6", "@iconify/icons-fa-brands": "^1.2.4", "@iconify/icons-fe": "^1.2.5", "@iconify/icons-flat-color-icons": "^1.2.5", "@iconify/icons-fluent": "^1.2.38", "@iconify/icons-fluent-mdl2": "^1.2.1", "@iconify/icons-ic": "^1.2.13", "@iconify/icons-icon-park-outline": "^1.2.11", "@iconify/icons-icon-park-twotone": "^1.2.8", "@iconify/icons-la": "^1.2.3", "@iconify/icons-logos": "^1.2.36", "@iconify/icons-lucide": "^1.2.135", "@iconify/icons-majesticons": "^1.2.6", "@iconify/icons-material-symbols": "^1.2.58", "@iconify/icons-mdi": "^1.2.48", "@iconify/icons-mingcute": "^1.2.9", "@iconify/icons-noto": "^1.2.10", "@iconify/icons-ph": "^1.2.5", "@iconify/icons-ri": "^1.2.10", "@iconify/icons-simple-icons": "^1.2.74", "@iconify/icons-skill-icons": "^1.2.1", "@iconify/icons-solar": "^1.2.3", "@iconify/icons-streamline": "^1.2.3", "@iconify/icons-tabler": "^1.2.95", "@iconify/icons-uiw": "^1.2.6", "@iconify/icons-vscode-icons": "^1.2.29", "@iconify/icons-wpf": "^1.2.3" } } ================================================ FILE: packages/icons/src/iconify/index.ts ================================================ import { createIconifyIcon } from '@vben-core/icons'; export * from '@vben-core/icons'; export const MdiKeyboardEsc = createIconifyIcon('mdi:keyboard-esc'); export const MdiWechat = createIconifyIcon('mdi:wechat'); export const MdiGithub = createIconifyIcon('mdi:github'); export const MdiGoogle = createIconifyIcon('mdi:google'); export const MdiQqchat = createIconifyIcon('mdi:qqchat'); export const EosSystem = createIconifyIcon('eos-icons:system-group'); // 个人中心 export const ProfileIcon = createIconifyIcon('mingcute:profile-line'); export const RiDingding = createIconifyIcon('ri:dingding-fill'); ================================================ FILE: packages/icons/src/iconify-offline/index.ts ================================================ import { createIconifyOfflineIcon } from '@vben-core/icons'; import githubOutlined from '@iconify/icons-ant-design/github-outlined'; import inboxIcon from '@iconify/icons-ant-design/inbox-outlined'; import userOutlined from '@iconify/icons-ant-design/user-outlined'; import ucIcon from '@iconify/icons-arcticons/uc-browser'; import defaultFileIcon from '@iconify/icons-bx/file'; import sqlIcon from '@iconify/icons-carbon/sql'; import linuxIcon from '@iconify/icons-devicon/linux'; import windowsIcon from '@iconify/icons-devicon/windows8'; import alipayIcon from '@iconify/icons-fa-brands/alipay'; import androidIcon from '@iconify/icons-flat-color-icons/android-os'; import comandLine from '@iconify/icons-flat-color-icons/command-line'; import folderIcon from '@iconify/icons-flat-color-icons/folder'; import defaultOsIcon from '@iconify/icons-ic/outline-computer'; import memoryIcon from '@iconify/icons-la/memory'; import chromeIcon from '@iconify/icons-logos/chrome'; import firefoxIcon from '@iconify/icons-logos/firefox'; import edgeIcon from '@iconify/icons-logos/microsoft-edge'; import operaIcon from '@iconify/icons-logos/opera'; import quarkIcon from '@iconify/icons-logos/quarkus-icon'; import redisIcon from '@iconify/icons-logos/redis'; import safariIcon from '@iconify/icons-logos/safari'; import vueIcon from '@iconify/icons-logos/vue'; import iphoneIcon from '@iconify/icons-majesticons/iphone-x-apps-line'; import menuIcon from '@iconify/icons-material-symbols/menu'; import okButtonIcon from '@iconify/icons-mdi/button-pointer'; import micromessengerIcon from '@iconify/icons-mdi/wechat'; import defaultBrowserIcon from '@iconify/icons-ph/browser-duotone'; import baiduIcon from '@iconify/icons-ri/baidu-fill'; import dingdingFill from '@iconify/icons-ri/dingding-fill'; import dingtalkIcon from '@iconify/icons-ri/dingding-line'; import taobaoIconFill from '@iconify/icons-ri/taobao-fill'; import giteeIcon from '@iconify/icons-simple-icons/gitee'; import qqIcon from '@iconify/icons-simple-icons/tencentqq'; import javaIcon from '@iconify/icons-skill-icons/java-light'; import tsIcon from '@iconify/icons-skill-icons/typescript'; import xmlIcon from '@iconify/icons-tabler/file-type-xml'; import githubOAuthIcon from '@iconify/icons-uiw/github'; import excelIcon from '@iconify/icons-vscode-icons/file-type-excel'; import osxIcon from '@iconify/icons-wpf/macos'; import './menu-icons'; // 用户 下拉菜单 export const GitHubOutlined = createIconifyOfflineIcon( 'ant-design:github-outlined', githubOutlined, ); export const UserOutlined = createIconifyOfflineIcon( 'ant-design:user-outlined', userOutlined, ); // 缓存监控使用 export const RedisIcon = createIconifyOfflineIcon('logos:redis', redisIcon); export const CommandLineIcon = createIconifyOfflineIcon( 'flat-color-icons:command-line', comandLine, ); export const MemoryIcon = createIconifyOfflineIcon('la:memory', memoryIcon); // 用户管理 导入 // Excel图标 export const ExcelIcon = createIconifyOfflineIcon( 'vscode-icons:file-type-excel', excelIcon, ); // 拖拽上传图标 export const InBoxIcon = createIconifyOfflineIcon( 'ant-design:inbox-outlined', inboxIcon, ); // 第三方登录相关图标 export const TaobaoIcon = createIconifyOfflineIcon( 'ri:taobao-fill', taobaoIconFill, ); export const AlipayIcon = createIconifyOfflineIcon( 'fa-brands:alipay', alipayIcon, ); export const DingdingIcon = createIconifyOfflineIcon( 'ri:dingding-fill', dingdingFill, ); export const GiteeIcon = createIconifyOfflineIcon( 'simple-icons:gitee', giteeIcon, ); export const GithubOAuthIcon = createIconifyOfflineIcon( 'uiw:github', githubOAuthIcon, ); // 系统相关图标 export const WindowsIcon = createIconifyOfflineIcon( 'devicon:windows8', windowsIcon, ); export const LinuxIcon = createIconifyOfflineIcon('devicon:linux', linuxIcon); export const OSXIcon = createIconifyOfflineIcon('wpf:macos', osxIcon); export const AndroidIcon = createIconifyOfflineIcon( 'flat-color-icons:android-os', androidIcon, ); export const IPhoneIcon = createIconifyOfflineIcon( 'majesticons:iphone-x-apps-line', iphoneIcon, ); // 上面图标没找到 默认图标 export const DefaultOsIcon = createIconifyOfflineIcon( 'ic:outline-computer', defaultOsIcon, ); // 浏览器相关图标 export const ChromeIcon = createIconifyOfflineIcon('logos:chrome', chromeIcon); export const EdgeIcon = createIconifyOfflineIcon( 'logos:microsoft-edge', edgeIcon, ); export const FirefoxIcon = createIconifyOfflineIcon( 'logos:firefox', firefoxIcon, ); export const OperaIcon = createIconifyOfflineIcon('logos:opera', operaIcon); export const SafariIcon = createIconifyOfflineIcon('logos:safari', safariIcon); export const MicromessengerIcon = createIconifyOfflineIcon( 'mdi:wechat', micromessengerIcon, ); export const QuarkIcon = createIconifyOfflineIcon( 'logos:quarkus-icon', quarkIcon, ); export const QQIcon = createIconifyOfflineIcon( 'simple-icons:tencentqq', qqIcon, ); export const DingtalkIcon = createIconifyOfflineIcon( 'ri:dingding-line', dingtalkIcon, ); export const UcIcon = createIconifyOfflineIcon('arcticons:uc-browser', ucIcon); export const BaiduIcon = createIconifyOfflineIcon('ri:baidu-fill', baiduIcon); // 未知浏览器图标 export const DefaultBrowserIcon = createIconifyOfflineIcon( 'ph:browser-duotone', defaultBrowserIcon, ); // 菜单类型 目录/按钮/菜单 export const FolderIcon = createIconifyOfflineIcon( 'flat-color-icons:folder', folderIcon, ); export const OkButtonIcon = createIconifyOfflineIcon( 'mdi:button-pointer', okButtonIcon, ); export const MenuIcon = createIconifyOfflineIcon( 'material-symbols:menu', menuIcon, ); export const JavaIcon = createIconifyOfflineIcon( 'skill-icons:java-light', javaIcon, ); export const XmlIcon = createIconifyOfflineIcon( 'tabler:file-type-xml', xmlIcon, ); export const SqlIcon = createIconifyOfflineIcon('carbon:sql', sqlIcon); export const TsIcon = createIconifyOfflineIcon( 'skill-icons:typescript', tsIcon, ); export const VueIcon = createIconifyOfflineIcon('logos:vue', vueIcon); export const DefaultFileIcon = createIconifyOfflineIcon( 'flat-color-icons:folder', defaultFileIcon, ); ================================================ FILE: packages/icons/src/iconify-offline/menu-icons.ts ================================================ import { addIcon } from '@vben-core/icons'; import schedule from '@iconify/icons-akar-icons/schedule'; import settingOutline from '@iconify/icons-ant-design/setting-outlined'; import antdTool from '@iconify/icons-ant-design/tool-outlined'; import UserAntd from '@iconify/icons-ant-design/user-outlined'; import Operation from '@iconify/icons-arcticons/one-hand-operation'; import BaseLineHousesFill from '@iconify/icons-bi/houses-fill'; import BxPackage from '@iconify/icons-bx/package'; import modelAlt from '@iconify/icons-carbon/model-alt'; import taskApproved from '@iconify/icons-carbon/task-approved'; import redisWordmark from '@iconify/icons-devicon/redis-wordmark'; import springWordmark from '@iconify/icons-devicon/spring-wordmark'; import vscode from '@iconify/icons-devicon/vscode'; import evergreenTree from '@iconify/icons-emojione/evergreen-tree'; import RoleBindingOutlined from '@iconify/icons-eos-icons/role-binding-outlined'; import SystemGroup from '@iconify/icons-eos-icons/system-group'; import NoticePush from '@iconify/icons-fe/notice-push'; import leave from '@iconify/icons-flat-color-icons/leave'; import plus from '@iconify/icons-flat-color-icons/plus'; import builDefinition from '@iconify/icons-fluent-mdl2/build-definition'; import Dictionary from '@iconify/icons-fluent-mdl2/dictionary'; import flow from '@iconify/icons-fluent-mdl2/flow'; import leaveUser from '@iconify/icons-fluent-mdl2/leave-user'; import from24 from '@iconify/icons-fluent/form-24-regular'; import BaseLineHouse from '@iconify/icons-ic/baseline-house'; import monitor from '@iconify/icons-ic/baseline-monitor'; import roundLaunch from '@iconify/icons-ic/round-launch'; import MenuSharp from '@iconify/icons-ic/sharp-menu'; import Appointment from '@iconify/icons-icon-park-outline/appointment'; import SettingTwo from '@iconify/icons-icon-park-twotone/setting-two'; import boolOpenText from '@iconify/icons-lucide/book-open-text'; import copyright from '@iconify/icons-lucide/copyright'; import table from '@iconify/icons-lucide/table'; import cloudDoneOutlineRounded from '@iconify/icons-material-symbols/cloud-done-outline-rounded'; import generatingTokensOutline from '@iconify/icons-material-symbols/generating-tokens-outline'; import LogoDevOutline from '@iconify/icons-material-symbols/logo-dev-outline'; import expressionIcon from '@iconify/icons-material-symbols/regular-expression-rounded'; import ccOutline from '@iconify/icons-mdi/cc-outline'; import tools from '@iconify/icons-mdi/tools'; import workflowOutline from '@iconify/icons-mdi/workflow-outline'; import DepartmentLine from '@iconify/icons-mingcute/department-line'; import profileLine from '@iconify/icons-mingcute/profile-line'; import UserDuotone from '@iconify/icons-ph/user-duotone'; import userList from '@iconify/icons-ph/user-list'; import users from '@iconify/icons-ph/users-light'; import insatnceLine from '@iconify/icons-ri/instance-line'; import todoLine from '@iconify/icons-ri/todo-line'; import Authy from '@iconify/icons-simple-icons/authy'; import FolderWithFilesOutline from '@iconify/icons-solar/folder-with-files-outline'; import monitorBoldDuotone from '@iconify/icons-solar/monitor-bold-duotone'; import monitorCameraOutlined from '@iconify/icons-solar/monitor-camera-outline'; import monitorPhoneOutlined from '@iconify/icons-solar/monitor-smartphone-outline'; import InterfaceLoginDialPadFingerPasswordDialPadDotFinger from '@iconify/icons-streamline/interface-login-dial-pad-finger-password-dial-pad-dot-finger'; import categoryPlus from '@iconify/icons-tabler/category-plus'; import code from '@iconify/icons-tabler/code'; /** * 这里添加菜单图标 */ addIcon('eos-icons:system-group', SystemGroup); addIcon('ph:user-duotone', UserDuotone); addIcon('ant-design:user-outlined', UserAntd); addIcon('eos-icons:role-binding-outlined', RoleBindingOutlined); addIcon('ic:sharp-menu', MenuSharp); addIcon('mingcute:department-line', DepartmentLine); addIcon('icon-park-outline:appointment', Appointment); addIcon('fluent-mdl2:dictionary', Dictionary); addIcon('icon-park-twotone:setting-two', SettingTwo); addIcon('fe:notice-push', NoticePush); addIcon('material-symbols:logo-dev-outline', LogoDevOutline); addIcon('arcticons:one-hand-operation', Operation); addIcon( 'streamline:interface-login-dial-pad-finger-password-dial-pad-dot-finger', InterfaceLoginDialPadFingerPasswordDialPadDotFinger, ); addIcon('solar:folder-with-files-outline', FolderWithFilesOutline); addIcon('simple-icons:authy', Authy); addIcon('solar:monitor-smartphone-outline', monitorPhoneOutlined); addIcon('ic:baseline-house', BaseLineHouse); addIcon('ph:users-light', users); addIcon('bi:houses-fill', BaseLineHousesFill); addIcon('ph:user-list', userList); addIcon('bx:package', BxPackage); addIcon('solar:monitor-bold-duotone', monitorBoldDuotone); addIcon('solar:monitor-camera-outline', monitorCameraOutlined); addIcon('material-symbols:generating-tokens-outline', generatingTokensOutline); addIcon('devicon:redis-wordmark', redisWordmark); addIcon('devicon:spring-wordmark', springWordmark); addIcon('akar-icons:schedule', schedule); addIcon('mdi:tools', tools); addIcon('ant-design:tool-outlined', antdTool); addIcon('tabler:code', code); addIcon('flat-color-icons:plus', plus); addIcon('devicon:vscode', vscode); addIcon('lucide:table', table); addIcon('emojione:evergreen-tree', evergreenTree); addIcon('fluent-mdl2:leave-user', leaveUser); addIcon('mdi:workflow-outline', workflowOutline); addIcon('tabler:category-plus', categoryPlus); addIcon('carbon:model-alt', modelAlt); addIcon('fluent-mdl2:build-definition', builDefinition); addIcon('fluent-mdl2:build-definition', builDefinition); addIcon('icon-park-outline:monitor', monitor); addIcon('ri:instance-line', insatnceLine); addIcon('ri:todo-line', todoLine); addIcon('fluent:form-24-regular', from24); addIcon('carbon:task-approved', taskApproved); addIcon('ic:round-launch', roundLaunch); addIcon('material-symbols:cloud-done-outline-rounded', cloudDoneOutlineRounded); addIcon('mdi:cc-outline', ccOutline); addIcon('lucide:book-open-text', boolOpenText); addIcon('lucide:copyright', copyright); // 个人中心 addIcon('mingcute:profile-line', profileLine); // oss配置 addIcon('ant-design:setting-outlined', settingOutline); // 请假 addIcon('flat-color-icons:leave', leave); // flow addIcon('fluent-mdl2:flow', flow); // 流程表达式 addIcon('material-symbols:regular-expression-rounded', expressionIcon); ================================================ FILE: packages/icons/src/icons/empty-icon.vue ================================================ ================================================ FILE: packages/icons/src/index.ts ================================================ export * from './iconify'; export * from './iconify-offline'; export { default as EmptyIcon } from './icons/empty-icon.vue'; export * from './svg'; export { VbenIcon } from '@vben-core/shadcn-ui'; ================================================ FILE: packages/icons/src/svg/index.ts ================================================ import { createIconifyIcon } from '@vben-core/icons'; import './load.js'; const SvgAvatar1Icon = createIconifyIcon('svg:avatar-1'); const SvgAvatar2Icon = createIconifyIcon('svg:avatar-2'); const SvgAvatar3Icon = createIconifyIcon('svg:avatar-3'); const SvgAvatar4Icon = createIconifyIcon('svg:avatar-4'); const SvgDownloadIcon = createIconifyIcon('svg:download'); const SvgCardIcon = createIconifyIcon('svg:card'); const SvgBellIcon = createIconifyIcon('svg:bell'); const SvgCakeIcon = createIconifyIcon('svg:cake'); const SvgAntdvLogoIcon = createIconifyIcon('svg:antdv-logo'); const SvgMaxKeyIcon = createIconifyIcon('svg:max-key'); const SvgTopiamIcon = createIconifyIcon('svg:topiam'); const SvgWechatIcon = createIconifyIcon('svg:wechat'); const SvgQQIcon = createIconifyIcon('svg:qq'); const SvgSnailJobIcon = createIconifyIcon('svg:snail-job'); export { SvgAntdvLogoIcon, SvgAvatar1Icon, SvgAvatar2Icon, SvgAvatar3Icon, SvgAvatar4Icon, SvgBellIcon, SvgCakeIcon, SvgCardIcon, SvgDownloadIcon, SvgMaxKeyIcon, SvgQQIcon, SvgSnailJobIcon, SvgTopiamIcon, SvgWechatIcon, }; export { default as SvgMessageUrl } from './icons/message.svg'; ================================================ FILE: packages/icons/src/svg/load.ts ================================================ import type { IconifyIconStructure } from '@vben-core/icons'; import { addIcon } from '@vben-core/icons'; let loaded = false; if (!loaded) { loadSvgIcons(); loaded = true; } function parseSvg(svgData: string): IconifyIconStructure { const parser = new DOMParser(); const xmlDoc = parser.parseFromString(svgData, 'image/svg+xml'); const svgElement = xmlDoc.documentElement; const svgContent = [...svgElement.childNodes] .filter((node) => node.nodeType === Node.ELEMENT_NODE) .map((node) => new XMLSerializer().serializeToString(node)) .join(''); const viewBoxValue = svgElement.getAttribute('viewBox') || ''; const [left, top, width, height] = viewBoxValue.split(' ').map((val) => { const num = Number(val); return Number.isNaN(num) ? undefined : num; }); return { body: svgContent, height, left, top, width, }; } /** * 自定义的svg图片转化为组件 * @example ./svg/avatar.svg * */ async function loadSvgIcons() { const svgEagers = import.meta.glob('./icons/**', { eager: true, query: '?raw', }); await Promise.all( Object.entries(svgEagers).map((svg) => { const [key, body] = svg as [string, { default: string } | string]; // ./icons/xxxx.svg => xxxxxx const start = key.lastIndexOf('/') + 1; const end = key.lastIndexOf('.'); const iconName = key.slice(start, end); return addIcon(`svg:${iconName}`, { ...parseSvg(typeof body === 'object' ? body.default : body), }); }), ); } ================================================ FILE: packages/icons/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/locales/package.json ================================================ { "name": "@vben/locales", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/locales" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@intlify/core-base": "catalog:", "@vben-core/composables": "workspace:*", "vue": "catalog:", "vue-i18n": "catalog:" } } ================================================ FILE: packages/locales/src/i18n.ts ================================================ import type { App } from 'vue'; import type { Locale } from 'vue-i18n'; import type { ImportLocaleFn, LoadMessageFn, LocaleSetupOptions, SupportedLanguagesType, } from './typing'; import { useSimpleLocale } from '@vben-core/composables'; import { unref } from 'vue'; import { createI18n } from 'vue-i18n'; const i18n = createI18n({ globalInjection: true, legacy: false, locale: '', messages: {}, }); const modules = import.meta.glob('./langs/**/*.json'); const { setSimpleLocale } = useSimpleLocale(); const localesMap = loadLocalesMapFromDir( /\.\/langs\/([^/]+)\/(.*)\.json$/, modules, ); let loadMessages: LoadMessageFn; /** * Load locale modules * @param modules */ function loadLocalesMap(modules: Record Promise>) { const localesMap: Record = {}; for (const [path, loadLocale] of Object.entries(modules)) { const key = path.match(/([\w-]*)\.(json)/)?.[1]; if (key) { localesMap[key] = loadLocale as ImportLocaleFn; } } return localesMap; } /** * Load locale modules with directory structure * @param regexp - Regular expression to match language and file names * @param modules - The modules object containing paths and import functions * @returns A map of locales to their corresponding import functions */ function loadLocalesMapFromDir( regexp: RegExp, modules: Record Promise>, ): Record { const localesRaw: Record Promise>> = {}; const localesMap: Record = {}; // Iterate over the modules to extract language and file names for (const path in modules) { const match = path.match(regexp); if (match) { const [_, locale, fileName] = match; if (locale && fileName) { if (!localesRaw[locale]) { localesRaw[locale] = {}; } if (modules[path]) { localesRaw[locale][fileName] = modules[path]; } } } } // Convert raw locale data into async import functions for (const [locale, files] of Object.entries(localesRaw)) { localesMap[locale] = async () => { const messages: Record = {}; for (const [fileName, importFn] of Object.entries(files)) { messages[fileName] = ((await importFn()) as any)?.default; } return { default: messages }; }; } return localesMap; } /** * Set i18n language * @param locale */ function setI18nLanguage(locale: Locale) { i18n.global.locale.value = locale; document?.querySelector('html')?.setAttribute('lang', locale); } async function setupI18n(app: App, options: LocaleSetupOptions = {}) { const { defaultLocale = 'zh-CN' } = options; // app可以自行扩展一些第三方库和组件库的国际化 loadMessages = options.loadMessages || (async () => ({})); app.use(i18n); await loadLocaleMessages(defaultLocale); // 在控制台打印警告 i18n.global.setMissingHandler((locale, key) => { if (options.missingWarn && key.includes('.')) { console.warn( `[intlify] Not found '${key}' key in '${locale}' locale messages.`, ); } }); } /** * Load locale messages * @param lang */ async function loadLocaleMessages(lang: SupportedLanguagesType) { if (unref(i18n.global.locale) === lang) { return setI18nLanguage(lang); } setSimpleLocale(lang); const message = await localesMap[lang]?.(); if (message?.default) { i18n.global.setLocaleMessage(lang, message.default); } const mergeMessage = await loadMessages(lang); i18n.global.mergeLocaleMessage(lang, mergeMessage); return setI18nLanguage(lang); } export { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, }; ================================================ FILE: packages/locales/src/index.ts ================================================ import { i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, } from './i18n'; const $t = i18n.global.t; const $te = i18n.global.te; export { $t, $te, i18n, loadLocaleMessages, loadLocalesMap, loadLocalesMapFromDir, setupI18n, }; export { type ImportLocaleFn, type LocaleSetupOptions, type SupportedLanguagesType, } from './typing'; export type { CompileError } from '@intlify/core-base'; export { I18nT, useI18n } from 'vue-i18n'; export type { Locale } from 'vue-i18n'; ================================================ FILE: packages/locales/src/langs/en-US/authentication.json ================================================ { "welcomeBack": "Welcome Back", "pageTitle": "Plug-and-play Admin system", "pageDesc": "Efficient, versatile frontend template", "loginSuccess": "Login Successful", "loginSuccessDesc": "Welcome Back", "loginSubtitle": "Enter your account details to manage your projects", "selectAccount": "Quick Select Account", "username": "Username", "password": "Password", "usernameTip": "Please enter username", "passwordErrorTip": "Password is incorrect", "passwordTip": "Please enter password", "verifyRequiredTip": "Please complete the verification first", "rememberMe": "Remember Me", "createAnAccount": "Create an Account", "createAccount": "Create Account", "alreadyHaveAccount": "Already have an account?", "accountTip": "Don't have an account?", "signUp": "Sign Up", "signUpSubtitle": "Make managing your applications simple and fun", "confirmPassword": "Confirm Password", "confirmPasswordTip": "The passwords do not match", "agree": "I agree to", "privacyPolicy": "Privacy-policy", "terms": "Terms", "agreeTip": "Please agree to the Privacy Policy and Terms", "goToLogin": "Login instead", "passwordStrength": "Use 8 or more characters with a mix of letters, numbers & symbols", "forgetPassword": "Forget Password?", "forgetPasswordSubtitle": "Enter your email and we'll send you instructions to reset your password", "emailTip": "Please enter email", "emailValidErrorTip": "The email format you entered is incorrect", "sendResetLink": "Send Reset Link", "email": "Email", "qrcodeSubtitle": "Scan the QR code with your phone to login", "qrcodePrompt": "Click 'Confirm' after scanning to complete login", "qrcodeLogin": "QR Code Login", "wechatLogin": "Wechat Login", "qqLogin": "QQ Login", "githubLogin": "Github Login", "googleLogin": "Google Login", "dingdingLogin": "Dingding Login", "codeSubtitle": "Enter your phone number to start managing your project", "code": "Security code", "codeTip": "Security code required {0} characters", "mobile": "Mobile", "mobileLogin": "Mobile Login", "mobileTip": "Please enter mobile number", "mobileErrortip": "The phone number format is incorrect", "sendCode": "Get Security code", "sendText": "Resend in {0}s", "thirdPartyLogin": "Or continue with", "weChat": "WeChat", "qq": "QQ", "gitHub": "GitHub", "google": "Google", "loginAgainTitle": "Please Log In Again", "loginAgainSubTitle": "Your login session has expired. Please log in again to continue.", "layout": { "center": "Align Center", "alignLeft": "Align Left", "alignRight": "Align Right" } } ================================================ FILE: packages/locales/src/langs/en-US/common.json ================================================ { "back": "Back", "backToHome": "Back To Home", "login": "Login", "logout": "Logout", "prompt": "Prompt", "cancel": "Cancel", "confirm": "Confirm", "reset": "Reset", "noData": "No Data", "refresh": "Refresh", "loadingMenu": "Loading Menu", "query": "Search", "search": "Search", "enabled": "Enabled", "disabled": "Disabled", "edit": "Edit", "delete": "Delete", "create": "Create", "yes": "Yes", "no": "No", "showSearchPanel": "Show search panel", "hideSearchPanel": "Hide search panel" } ================================================ FILE: packages/locales/src/langs/en-US/preferences.json ================================================ { "title": "Preferences", "subtitle": "Customize Preferences & Preview in Real Time", "enableStickyPreferencesNavigationBar": "Enable sticky preferences navigation bar", "disableStickyPreferencesNavigationBar": "Disable sticky preferences navigation bar", "resetTip": "Data has changed, click to reset", "resetTitle": "Reset Preferences", "resetSuccess": "Preferences reset successfully", "appearance": "Appearance", "layout": "Layout", "content": "Content", "other": "Other", "wide": "Wide", "compact": "Fixed", "followSystem": "Follow System", "vertical": "Vertical", "verticalTip": "Side vertical menu mode", "horizontal": "Horizontal", "horizontalTip": "Horizontal menu mode, all menus displayed at the top", "twoColumn": "Two Column", "twoColumnTip": "Vertical Two Column Menu Mode", "headerSidebarNav": "Header Vertical", "headerSidebarNavTip": "Header Full Width, Sidebar Navigation Mode", "headerTwoColumn": "Header Two Column", "headerTwoColumnTip": "Header Navigation & Sidebar Two Column co-exists", "mixedMenu": "Mixed Menu", "mixedMenuTip": "Vertical & Horizontal Menu Co-exists", "fullContent": "Full Content", "fullContentTip": "Only display content body, hide all menus", "normal": "Normal", "plain": "Plain", "rounded": "Rounded", "copyPreferences": "Copy Preferences", "copyPreferencesSuccessTitle": "Copy successful", "copyPreferencesSuccess": "Copy successful, please override in `src/preferences.ts` under app", "clearAndLogout": "Clear Cache & Logout", "mode": "Mode", "general": "General", "language": "Language", "dynamicTitle": "Dynamic Title", "watermark": "Watermark", "watermarkContent": "Please input Watermark content", "checkUpdates": "Periodic update check", "position": { "title": "Preferences Postion", "header": "Header", "auto": "Auto", "fixed": "Fixed" }, "sidebar": { "buttons": "Show Buttons", "buttonFixed": "Fixed", "buttonCollapsed": "Collapsed", "title": "Sidebar", "width": "Width", "visible": "Show Sidebar", "collapsed": "Collpase Menu", "collapsedShowTitle": "Show Menu Title", "autoActivateChild": "Auto Activate SubMenu", "autoActivateChildTip": "`Enabled` to automatically activate the submenu while click menu.", "expandOnHover": "Expand On Hover", "expandOnHoverTip": "When the mouse hovers over menu, \n `Enabled` to expand children menus \n `Disabled` to expand whole sidebar." }, "tabbar": { "title": "Tabbar", "enable": "Enable Tab Bar", "icon": "Show Tabbar Icon", "showMore": "Show More Button", "showMaximize": "Show Maximize Button", "persist": "Persist Tabs", "maxCount": "Max Count of Tabs", "maxCountTip": "When the number of tabs exceeds the maximum,\nthe oldest tab will be closed.\n Set to 0 to disable count checking.", "draggable": "Enable Draggable Sort", "wheelable": "Support Mouse Wheel", "middleClickClose": "Close Tab when Mouse Middle Button Click", "wheelableTip": "When enabled, the Tabbar area responds to vertical scrolling events of the scroll wheel.", "styleType": { "title": "Tabs Style", "chrome": "Chrome", "card": "Card", "plain": "Plain", "brisk": "Brisk" }, "contextMenu": { "reload": "Reload", "close": "Close", "pin": "Pin", "unpin": "Unpin", "closeLeft": "Close Left Tabs", "closeRight": "Close Right Tabs", "closeOther": "Close Other Tabs", "closeAll": "Close All Tabs", "openInNewWindow": "Open in New Window", "maximize": "Maximize", "restoreMaximize": "Restore" } }, "navigationMenu": { "title": "Navigation Menu", "style": "Navigation Menu Style", "accordion": "Sidebar Accordion Menu", "split": "Navigation Menu Separation", "splitTip": "When enabled, the sidebar displays the top bar's submenu" }, "breadcrumb": { "title": "Breadcrumb", "home": "Show Home Button", "enable": "Enable Breadcrumb", "icon": "Show Breadcrumb Icon", "background": "background", "style": "Breadcrumb Style", "hideOnlyOne": "Hidden when only one" }, "animation": { "title": "Animation", "loading": "Page Loading", "transition": "Page Transition", "progress": "Page Progress" }, "theme": { "title": "Theme", "radius": "Radius", "light": "Light", "dark": "Dark", "darkSidebar": "Semi Dark Sidebar", "darkHeader": "Semi Dark Header", "weakMode": "Weak Mode", "grayMode": "Gray Mode", "builtin": { "title": "Built-in", "default": "Default", "violet": "Violet", "pink": "Pink", "rose": "Rose", "skyBlue": "Sky Blue", "deepBlue": "Deep Blue", "green": "Green", "deepGreen": "Deep Green", "orange": "Orange", "yellow": "Yellow", "zinc": "Zinc", "neutral": "Neutral", "slate": "Slate", "gray": "Gray", "custom": "Custom" } }, "header": { "title": "Header", "visible": "Show Header", "modeStatic": "Static", "modeFixed": "Fixed", "modeAuto": "Auto hide & Show", "modeAutoScroll": "Scroll to Hide & Show", "menuAlign": "Menu Align", "menuAlignStart": "Start", "menuAlignEnd": "End", "menuAlignCenter": "Center" }, "footer": { "title": "Footer", "visible": "Show Footer", "fixed": "Fixed at Bottom" }, "copyright": { "title": "Copyright", "enable": "Enable Copyright", "companyName": "Company Name", "companySiteLink": "Company Site Link", "date": "Date", "icp": "ICP License Number", "icpLink": "ICP Site Link" }, "shortcutKeys": { "title": "Shortcut Keys", "global": "Global", "search": "Global Search", "logout": "Logout", "preferences": "Preferences" }, "widget": { "title": "Widget", "globalSearch": "Enable Global Search", "fullscreen": "Enable Fullscreen", "themeToggle": "Enable Theme Toggle", "languageToggle": "Enable Language Toggle", "notification": "Enable Notification", "sidebarToggle": "Enable Sidebar Toggle", "lockScreen": "Enable Lock Screen", "refresh": "Enable Refresh" } } ================================================ FILE: packages/locales/src/langs/en-US/ui.json ================================================ { "formRules": { "required": "Please enter {0}", "selectRequired": "Please select {0}", "minLength": "{0} must be at least {1} characters", "maxLength": "{0} can be at most {1} characters", "length": "{0} must be {1} characters long", "alreadyExists": "{0} `{1}` already exists", "startWith": "{0} must start with `{1}`", "invalidURL": "Please input a valid URL" }, "actionTitle": { "edit": "Modify {0}", "create": "Create {0}", "delete": "Delete {0}", "view": "View {0}" }, "actionMessage": { "deleteConfirm": "Are you sure to delete {0}?", "deleting": "Deleting {0} ...", "deleteSuccess": "{0} deleted successfully", "operationSuccess": "Operation succeeded", "operationFailed": "Operation failed" }, "placeholder": { "input": "Please enter", "select": "Please select" }, "captcha": { "title": "Please complete the security verification", "sliderSuccessText": "Passed", "sliderDefaultText": "Slider and drag", "alt": "Supports img tag src attribute value", "sliderRotateDefaultTip": "Click picture to refresh", "sliderTranslateDefaultTip": "Click picture to refresh", "sliderRotateFailTip": "Validation failed", "sliderRotateSuccessTip": "Validation successful, time {0} seconds", "sliderTranslateFailTip": "Validation failed", "sliderTranslateSuccessTip": "Validation successful, time {0} seconds", "refreshAriaLabel": "Refresh captcha", "confirmAriaLabel": "Confirm selection", "confirm": "Confirm", "pointAriaLabel": "Click point", "clickInOrder": "Please click in order" }, "iconPicker": { "placeholder": "Select an icon", "search": "Search icon..." }, "jsonViewer": { "copy": "Copy", "copied": "Copied" }, "fallback": { "pageNotFound": "Oops! Page Not Found", "pageNotFoundDesc": "Sorry, we couldn't find the page you were looking for.", "forbidden": "Oops! Access Denied", "forbiddenDesc": "Sorry, but you don't have permission to access this page.", "internalError": "Oops! Something Went Wrong", "internalErrorDesc": "Sorry, but the server encountered an error.", "offline": "Offline Page", "offlineError": "Oops! Network Error", "offlineErrorDesc": "Sorry, can't connect to the internet. Check your connection.", "comingSoon": "Coming Soon", "http": { "requestTimeout": "The request timed out. Please try again later.", "networkError": "A network error occurred. Please check your internet connection and try again.", "badRequest": "Bad Request. Please check your input and try again.", "unauthorized": "Unauthorized. Please log in to continue.", "forbidden": "Forbidden. You do not have permission to access this resource.", "notFound": "Not Found. The requested resource could not be found.", "internalServerError": "Internal Server Error. Something went wrong on our end. Please try again later." } }, "widgets": { "document": "Document", "profile": "Profile", "qa": "Q&A", "setting": "Settings", "logoutTip": "Do you want to logout?", "viewAll": "View All Messages", "notifications": "Notifications", "markAllAsRead": "Make All as Read", "clearNotifications": "Clear", "checkUpdatesTitle": "New Version Available", "checkUpdatesDescription": "Click to refresh and get the latest version", "search": { "title": "Search", "searchNavigate": "Search Navigation", "select": "Select", "navigate": "Navigate", "close": "Close", "noResults": "No Search Results Found", "noRecent": "No Search History", "recent": "Search History" }, "lockScreen": { "title": "Lock Screen", "screenButton": "Locking", "password": "Password", "placeholder": "Please enter password", "unlock": "Click to unlock", "errorPasswordTip": "Password error, please re-enter", "backToLogin": "Back to login", "entry": "Enter the system" } } } ================================================ FILE: packages/locales/src/langs/zh-CN/authentication.json ================================================ { "welcomeBack": "欢迎回来", "pageTitle": "开箱即用的大型中后台管理系统", "pageDesc": "工程化、高性能、跨组件库的前端模版", "loginSuccess": "登录成功", "loginSuccessDesc": "欢迎回来", "loginSubtitle": "请输入您的帐户信息以开始管理您的项目", "selectAccount": "快速选择账号", "username": "账号", "password": "密码", "usernameTip": "请输入用户名", "passwordTip": "请输入密码", "verifyRequiredTip": "请先完成验证", "passwordErrorTip": "密码错误", "rememberMe": "记住账号", "createAnAccount": "创建一个账号", "createAccount": "创建账号", "alreadyHaveAccount": "已经有账号了?", "accountTip": "还没有账号?", "signUp": "注册", "signUpSubtitle": "让您的应用程序管理变得简单而有趣", "confirmPassword": "确认密码", "confirmPasswordTip": "两次输入的密码不一致", "agree": "我同意", "privacyPolicy": "隐私政策", "terms": "条款", "agreeTip": "请同意隐私政策和条款", "goToLogin": "去登录", "passwordStrength": "使用 8 个或更多字符,混合字母、数字和符号", "forgetPassword": "忘记密码?", "forgetPasswordSubtitle": "输入您的电子邮件,我们将向您发送重置密码的连接", "emailTip": "请输入邮箱", "emailValidErrorTip": "你输入的邮箱格式不正确", "sendResetLink": "发送重置链接", "email": "邮箱", "qrcodeSubtitle": "请用手机扫描二维码登录", "qrcodePrompt": "扫码后点击 '确认',即可完成登录", "qrcodeLogin": "扫码登录", "wechatLogin": "微信登录", "qqLogin": "QQ登录", "githubLogin": "Github登录", "googleLogin": "Google登录", "dingdingLogin": "钉钉登录", "codeSubtitle": "请输入您的手机号码以开始管理您的项目", "code": "验证码", "codeTip": "请输入{0}位验证码", "mobile": "手机号码", "mobileTip": "请输入手机号", "mobileErrortip": "手机号码格式错误", "mobileLogin": "手机号登录", "sendCode": "获取验证码", "sendText": "{0}秒后重新获取", "thirdPartyLogin": "其他登录方式", "weChat": "微信", "qq": "QQ", "gitHub": "GitHub", "google": "Google", "loginAgainTitle": "重新登录", "loginAgainSubTitle": "您的登录状态已过期,请重新登录以继续。", "layout": { "center": "居中", "alignLeft": "居左", "alignRight": "居右" } } ================================================ FILE: packages/locales/src/langs/zh-CN/common.json ================================================ { "back": "返回", "backToHome": "返回首页", "login": "登录", "logout": "退出登录", "prompt": "提示", "cancel": "取消", "confirm": "确认", "reset": "重置", "noData": "暂无数据", "refresh": "刷新", "loadingMenu": "加载菜单中", "query": "查询", "search": "搜索", "enabled": "已启用", "disabled": "已禁用", "edit": "修改", "delete": "删除", "create": "新增", "yes": "是", "no": "否", "showSearchPanel": "显示搜索面板", "hideSearchPanel": "隐藏搜索面板" } ================================================ FILE: packages/locales/src/langs/zh-CN/preferences.json ================================================ { "title": "偏好设置", "subtitle": "自定义偏好设置 & 实时预览", "enableStickyPreferencesNavigationBar": "开启首选项导航栏吸顶效果", "disableStickyPreferencesNavigationBar": "关闭首选项导航栏吸顶效果", "resetTitle": "重置偏好设置", "resetTip": "数据有变化,点击可进行重置", "resetSuccess": "重置偏好设置成功", "appearance": "外观", "layout": "布局", "content": "内容", "other": "其它", "wide": "流式", "compact": "定宽", "followSystem": "跟随系统", "vertical": "垂直", "verticalTip": "侧边垂直菜单模式", "horizontal": "水平", "horizontalTip": "水平菜单模式,菜单全部显示在顶部", "twoColumn": "双列菜单", "twoColumnTip": "垂直双列菜单模式", "headerSidebarNav": "侧边导航", "headerSidebarNavTip": "顶部通栏,侧边导航模式", "headerTwoColumn": "混合双列", "headerTwoColumnTip": "双列、水平菜单共存模式", "mixedMenu": "混合垂直", "mixedMenuTip": "垂直水平菜单共存", "fullContent": "内容全屏", "fullContentTip": "不显示任何菜单,只显示内容主体", "normal": "常规", "plain": "朴素", "rounded": "圆润", "copyPreferences": "复制偏好设置", "copyPreferencesSuccessTitle": "复制成功", "copyPreferencesSuccess": "复制成功,请在 app 下的 `src/preferences.ts`内进行覆盖", "clearAndLogout": "清空缓存 & 退出登录", "mode": "模式", "general": "通用", "language": "语言", "dynamicTitle": "动态标题", "watermark": "水印", "watermarkContent": "请输入水印文案", "checkUpdates": "定时检查更新", "position": { "title": "偏好设置位置", "header": "顶栏", "auto": "自动", "fixed": "固定" }, "sidebar": { "buttons": "显示按钮", "buttonFixed": "固定按钮", "buttonCollapsed": "折叠按钮", "title": "侧边栏", "width": "宽度", "visible": "显示侧边栏", "collapsed": "折叠菜单", "collapsedShowTitle": "折叠显示菜单名", "autoActivateChild": "自动激活子菜单", "autoActivateChildTip": "点击顶层菜单时,自动激活第一个子菜单或者上一次激活的子菜单", "expandOnHover": "鼠标悬停展开", "expandOnHoverTip": "鼠标在折叠区域悬浮时,`启用`则展开当前子菜单,`禁用`则展开整个侧边栏" }, "tabbar": { "title": "标签栏", "enable": "启用标签栏", "icon": "显示标签栏图标", "showMore": "显示更多按钮", "showMaximize": "显示最大化按钮", "persist": "持久化标签页", "maxCount": "最大标签数", "maxCountTip": "每次打开新的标签时如果超过最大标签数,\n会自动关闭一个最先打开的标签\n设置为 0 则不限制", "draggable": "启动拖拽排序", "wheelable": "启用纵向滚轮响应", "middleClickClose": "点击鼠标中键关闭标签页", "wheelableTip": "开启后,标签栏区域可以响应滚轮的纵向滚动事件。\n关闭时,只能响应系统的横向滚动事件(需要按下Shift再滚动滚轮)", "styleType": { "title": "标签页风格", "chrome": "谷歌", "card": "卡片", "plain": "朴素", "brisk": "轻快" }, "contextMenu": { "reload": "重新加载", "close": "关闭", "pin": "固定", "unpin": "取消固定", "closeLeft": "关闭左侧标签页", "closeRight": "关闭右侧标签页", "closeOther": "关闭其它标签页", "closeAll": "关闭全部标签页", "openInNewWindow": "在新窗口打开", "maximize": "最大化", "restoreMaximize": "还原" } }, "navigationMenu": { "title": "导航菜单", "style": "导航菜单风格", "accordion": "侧边导航菜单手风琴模式", "split": "导航菜单分离", "splitTip": "开启时,侧边栏显示顶栏对应菜单的子菜单" }, "breadcrumb": { "title": "面包屑导航", "enable": "开启面包屑导航", "icon": "显示面包屑图标", "home": "显示首页按钮", "style": "面包屑风格", "hideOnlyOne": "仅有一个时隐藏", "background": "背景" }, "animation": { "title": "动画", "loading": "页面切换 Loading", "transition": "页面切换动画", "progress": "页面切换进度条" }, "theme": { "title": "主题", "radius": "圆角", "light": "浅色", "dark": "深色", "darkSidebar": "深色侧边栏", "darkHeader": "深色顶栏", "weakMode": "色弱模式", "grayMode": "灰色模式", "builtin": { "title": "内置主题", "default": "默认", "violet": "紫罗兰", "pink": "樱花粉", "rose": "玫瑰红", "skyBlue": "天蓝色", "deepBlue": "深蓝色", "green": "浅绿色", "deepGreen": "深绿色", "orange": "橙黄色", "yellow": "柠檬黄", "zinc": "锌色灰", "neutral": "中性色", "slate": "石板灰", "gray": "中灰色", "custom": "自定义" } }, "header": { "title": "顶栏", "modeStatic": "静止", "modeFixed": "固定", "modeAuto": "自动隐藏和显示", "modeAutoScroll": "滚动隐藏和显示", "visible": "显示顶栏", "menuAlign": "菜单位置", "menuAlignStart": "左侧", "menuAlignEnd": "右侧", "menuAlignCenter": "居中" }, "footer": { "title": "底栏", "visible": "显示底栏", "fixed": "固定在底部" }, "copyright": { "title": "版权", "enable": "启用版权", "companyName": "公司名", "companySiteLink": "公司主页", "date": "日期", "icp": "ICP 备案号", "icpLink": "ICP 网站链接" }, "shortcutKeys": { "title": "快捷键", "global": "全局", "search": "全局搜索", "logout": "退出登录", "preferences": "偏好设置" }, "widget": { "title": "小部件", "globalSearch": "启用全局搜索", "fullscreen": "启用全屏", "themeToggle": "启用主题切换", "languageToggle": "启用语言切换", "notification": "启用通知", "sidebarToggle": "启用侧边栏切换", "lockScreen": "启用锁屏", "refresh": "启用刷新" } } ================================================ FILE: packages/locales/src/langs/zh-CN/ui.json ================================================ { "formRules": { "required": "请输入{0}", "selectRequired": "请选择{0}", "minLength": "{0}至少{1}个字符", "maxLength": "{0}最多{1}个字符", "length": "{0}长度必须为{1}个字符", "alreadyExists": "{0} `{1}` 已存在", "startWith": "{0}必须以 {1} 开头", "invalidURL": "请输入有效的链接" }, "actionTitle": { "edit": "修改{0}", "create": "新增{0}", "delete": "删除{0}", "view": "查看{0}" }, "actionMessage": { "deleteConfirm": "确定删除 {0} 吗?", "deleting": "正在删除 {0} ...", "deleteSuccess": "{0} 删除成功", "operationSuccess": "操作成功", "operationFailed": "操作失败" }, "placeholder": { "input": "请输入", "select": "请选择" }, "captcha": { "title": "请完成安全验证", "sliderSuccessText": "验证通过", "sliderDefaultText": "请按住滑块拖动", "sliderRotateDefaultTip": "点击图片可刷新", "sliderTranslateDefaultTip": "点击图片可刷新", "sliderRotateFailTip": "验证失败", "sliderRotateSuccessTip": "验证成功,耗时{0}秒", "sliderTranslateFailTip": "验证失败", "sliderTranslateSuccessTip": "验证成功,耗时{0}秒", "alt": "支持img标签src属性值", "refreshAriaLabel": "刷新验证码", "confirmAriaLabel": "确认选择", "confirm": "确认", "pointAriaLabel": "点击点", "clickInOrder": "请依次点击" }, "iconPicker": { "placeholder": "选择一个图标", "search": "搜索图标..." }, "jsonViewer": { "copy": "复制", "copied": "已复制" }, "fallback": { "pageNotFound": "哎呀!未找到页面", "pageNotFoundDesc": "抱歉,我们无法找到您要找的页面。", "forbidden": "哎呀!访问被拒绝", "forbiddenDesc": "抱歉,您没有权限访问此页面。", "internalError": "哎呀!出错了", "internalErrorDesc": "抱歉,服务器遇到错误。", "offline": "离线页面", "offlineError": "哎呀!网络错误", "offlineErrorDesc": "抱歉,无法连接到互联网,请检查您的网络连接并重试。", "comingSoon": "即将推出", "http": { "requestTimeout": "请求超时,请稍后再试。", "networkError": "网络异常,请检查您的网络连接后重试。", "badRequest": "请求错误。请检查您的输入并重试。", "unauthorized": "登录认证过期,请重新登录后继续。", "forbidden": "禁止访问, 您没有权限访问此资源。", "notFound": "未找到, 请求的资源不存在。", "internalServerError": "内部服务器错误,请稍后再试。" } }, "widgets": { "document": "文档", "profile": "个人中心", "qa": "问题 & 帮助", "setting": "设置", "logoutTip": "是否退出登录?", "viewAll": "查看所有消息", "notifications": "通知", "markAllAsRead": "全部标记为已读", "clearNotifications": "清空", "checkUpdatesTitle": "新版本可用", "checkUpdatesDescription": "点击刷新以获取最新版本", "search": { "title": "搜索", "searchNavigate": "搜索导航菜单", "select": "选择", "navigate": "导航", "close": "关闭", "noResults": "未找到搜索结果", "noRecent": "没有搜索历史", "recent": "搜索历史" }, "lockScreen": { "title": "锁定屏幕", "screenButton": "锁定", "password": "密码", "placeholder": "请输入锁屏密码", "unlock": "点击解锁", "errorPasswordTip": "密码错误,请重新输入", "backToLogin": "返回登录", "entry": "进入系统" } } } ================================================ FILE: packages/locales/src/typing.ts ================================================ export type SupportedLanguagesType = 'en-US' | 'zh-CN'; export type ImportLocaleFn = () => Promise<{ default: Record }>; export type LoadMessageFn = ( lang: SupportedLanguagesType, ) => Promise | undefined>; export interface LocaleSetupOptions { /** * Default language * @default zh-CN */ defaultLocale?: SupportedLanguagesType; /** * Load message function * @param lang * @returns */ loadMessages?: LoadMessageFn; /** * Whether to warn when the key is not found */ missingWarn?: boolean; } ================================================ FILE: packages/locales/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/preferences/package.json ================================================ { "name": "@vben/preferences", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/preferences" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben-core/preferences": "workspace:*", "@vben-core/typings": "workspace:*" } } ================================================ FILE: packages/preferences/src/index.ts ================================================ import type { Preferences } from '@vben-core/preferences'; import type { DeepPartial } from '@vben-core/typings'; /** * 如果你想所有的app都使用相同的默认偏好设置,你可以在这里定义 * 而不是去修改 @vben-core/preferences 中的默认偏好设置 * @param preferences * @returns */ function defineOverridesPreferences(preferences: DeepPartial) { return preferences; } export { defineOverridesPreferences }; export * from '@vben-core/preferences'; ================================================ FILE: packages/preferences/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/stores/package.json ================================================ { "name": "@vben/stores", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/stores" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben-core/preferences": "workspace:*", "@vben-core/shared": "workspace:*", "@vben-core/typings": "workspace:*", "pinia": "catalog:", "pinia-plugin-persistedstate": "catalog:", "secure-ls": "catalog:", "vue": "catalog:", "vue-router": "catalog:" } } ================================================ FILE: packages/stores/shim-pinia.d.ts ================================================ // https://github.com/vuejs/pinia/issues/2098 declare module 'pinia' { export function acceptHMRUpdate( initialUseStore: any | StoreDefinition, hot: any, ): (newModule: any) => any; } export {}; ================================================ FILE: packages/stores/src/index.ts ================================================ export * from './modules'; export * from './setup'; export { defineStore, storeToRefs } from 'pinia'; ================================================ FILE: packages/stores/src/modules/access.test.ts ================================================ import { createPinia, setActivePinia } from 'pinia'; import { beforeEach, describe, expect, it } from 'vitest'; import { useAccessStore } from './access'; describe('useAccessStore', () => { beforeEach(() => { setActivePinia(createPinia()); }); it('updates accessMenus state', () => { const store = useAccessStore(); expect(store.accessMenus).toEqual([]); store.setAccessMenus([{ name: 'Dashboard', path: '/dashboard' }]); expect(store.accessMenus).toEqual([ { name: 'Dashboard', path: '/dashboard' }, ]); }); it('updates accessToken state correctly', () => { const store = useAccessStore(); expect(store.accessToken).toBeNull(); // 初始状态 store.setAccessToken('abc123'); expect(store.accessToken).toBe('abc123'); }); it('returns the correct accessToken', () => { const store = useAccessStore(); store.setAccessToken('xyz789'); expect(store.accessToken).toBe('xyz789'); }); // 测试设置空的访问菜单列表 it('handles empty accessMenus correctly', () => { const store = useAccessStore(); store.setAccessMenus([]); expect(store.accessMenus).toEqual([]); }); // 测试设置空的访问路由列表 it('handles empty accessRoutes correctly', () => { const store = useAccessStore(); store.setAccessRoutes([]); expect(store.accessRoutes).toEqual([]); }); }); ================================================ FILE: packages/stores/src/modules/access.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import type { MenuRecordRaw } from '@vben-core/typings'; import { acceptHMRUpdate, defineStore } from 'pinia'; type AccessToken = null | string; interface AccessState { /** * 权限码 */ accessCodes: string[]; /** * 可访问的菜单列表 */ accessMenus: MenuRecordRaw[]; /** * 可访问的路由列表 */ accessRoutes: RouteRecordRaw[]; /** * 登录 accessToken */ accessToken: AccessToken; /** * 是否已经检查过权限 */ isAccessChecked: boolean; /** * 是否锁屏状态 */ isLockScreen: boolean; /** * 锁屏密码 */ lockScreenPassword?: string; /** * 登录是否过期 */ loginExpired: boolean; /** * 登录 accessToken */ refreshToken: AccessToken; } /** * @zh_CN 访问权限相关 */ export const useAccessStore = defineStore('core-access', { actions: { getMenuByPath(path: string) { function findMenu( menus: MenuRecordRaw[], path: string, ): MenuRecordRaw | undefined { for (const menu of menus) { if (menu.path === path) { return menu; } if (menu.children) { const matched = findMenu(menu.children, path); if (matched) { return matched; } } } } return findMenu(this.accessMenus, path); }, lockScreen(password: string) { this.isLockScreen = true; this.lockScreenPassword = password; }, setAccessCodes(codes: string[]) { this.accessCodes = codes; }, setAccessMenus(menus: MenuRecordRaw[]) { this.accessMenus = menus; }, setAccessRoutes(routes: RouteRecordRaw[]) { this.accessRoutes = routes; }, setAccessToken(token: AccessToken) { this.accessToken = token; }, setIsAccessChecked(isAccessChecked: boolean) { this.isAccessChecked = isAccessChecked; }, setLoginExpired(loginExpired: boolean) { this.loginExpired = loginExpired; }, setRefreshToken(token: AccessToken) { this.refreshToken = token; }, unlockScreen() { this.isLockScreen = false; this.lockScreenPassword = undefined; }, }, persist: { // 持久化 pick: [ 'accessToken', 'refreshToken', 'accessCodes', 'isLockScreen', 'lockScreenPassword', ], }, state: (): AccessState => ({ accessCodes: [], accessMenus: [], accessRoutes: [], accessToken: null, isAccessChecked: false, isLockScreen: false, lockScreenPassword: undefined, loginExpired: false, refreshToken: null, }), }); // 解决热更新问题 const hot = import.meta.hot; if (hot) { hot.accept(acceptHMRUpdate(useAccessStore, hot)); } ================================================ FILE: packages/stores/src/modules/index.ts ================================================ export * from './access'; export * from './tabbar'; export * from './user'; ================================================ FILE: packages/stores/src/modules/tabbar.test.ts ================================================ import { createRouter, createWebHistory } from 'vue-router'; import { createPinia, setActivePinia } from 'pinia'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { useTabbarStore } from './tabbar'; describe('useAccessStore', () => { const router = createRouter({ history: createWebHistory(), routes: [], }); router.push = vi.fn(); router.replace = vi.fn(); beforeEach(() => { setActivePinia(createPinia()); vi.clearAllMocks(); }); it('adds a new tab', () => { const store = useTabbarStore(); const tab: any = { fullPath: '/home', meta: {}, key: '/home', name: 'Home', path: '/home', }; const addNewTab = store.addTab(tab); expect(store.tabs.length).toBe(1); expect(store.tabs[0]).toEqual(addNewTab); }); it('adds a new tab if it does not exist', () => { const store = useTabbarStore(); const newTab: any = { fullPath: '/new', meta: {}, name: 'New', path: '/new', }; const addNewTab = store.addTab(newTab); expect(store.tabs).toContainEqual(addNewTab); }); it('updates an existing tab instead of adding a new one', () => { const store = useTabbarStore(); const initialTab: any = { fullPath: '/existing', meta: { fullPathKey: false, }, name: 'Existing', path: '/existing', query: {}, }; store.addTab(initialTab); const updatedTab = { ...initialTab, query: { id: '1' } }; store.addTab(updatedTab); expect(store.tabs.length).toBe(1); expect(store.tabs[0]?.query).toEqual({ id: '1' }); }); it('closes all tabs', async () => { const store = useTabbarStore(); store.addTab({ fullPath: '/home', meta: {}, name: 'Home', path: '/home', } as any); router.replace = vi.fn(); await store.closeAllTabs(router); expect(store.tabs.length).toBe(1); }); it('closes a non-affix tab', () => { const store = useTabbarStore(); const tab: any = { fullPath: '/closable', meta: {}, name: 'Closable', path: '/closable', }; store.tabs.push(tab); store._close(tab); expect(store.tabs.length).toBe(0); }); it('does not close an affix tab', () => { const store = useTabbarStore(); const affixTab: any = { fullPath: '/affix', meta: { affixTab: true }, name: 'Affix', path: '/affix', }; store.tabs.push(affixTab); store._close(affixTab); expect(store.tabs.length).toBe(1); // Affix tab should not be closed }); it('returns all cache tabs', () => { const store = useTabbarStore(); store.cachedTabs.add('Home'); store.cachedTabs.add('About'); expect(store.getCachedTabs).toEqual(['Home', 'About']); }); it('returns all tabs, including affix tabs', () => { const store = useTabbarStore(); const normalTab: any = { fullPath: '/normal', meta: {}, name: 'Normal', path: '/normal', }; const affixTab: any = { fullPath: '/affix', meta: { affixTab: true }, name: 'Affix', path: '/affix', }; store.tabs.push(normalTab); store.affixTabs.push(affixTab); expect(store.getTabs).toContainEqual(normalTab); expect(store.affixTabs).toContainEqual(affixTab); }); it('navigates to a specific tab', async () => { const store = useTabbarStore(); const tab: any = { meta: {}, name: 'Dashboard', path: '/dashboard' }; await store._goToTab(tab, router); expect(router.replace).toHaveBeenCalledWith({ params: {}, path: '/dashboard', query: {}, }); }); it('closes multiple tabs by paths', async () => { const store = useTabbarStore(); store.addTab({ fullPath: '/home', meta: {}, name: 'Home', path: '/home', } as any); store.addTab({ fullPath: '/about', meta: {}, name: 'About', path: '/about', } as any); store.addTab({ fullPath: '/contact', meta: {}, name: 'Contact', path: '/contact', } as any); await store._bulkCloseByKeys(['/home', '/contact']); expect(store.tabs).toHaveLength(1); expect(store.tabs[0]?.name).toBe('About'); }); it('closes all tabs to the left of the specified tab', async () => { const store = useTabbarStore(); store.addTab({ fullPath: '/home', meta: {}, name: 'Home', path: '/home', } as any); store.addTab({ fullPath: '/about', meta: {}, name: 'About', path: '/about', } as any); const targetTab: any = { fullPath: '/contact', meta: {}, name: 'Contact', path: '/contact', }; const addTargetTab = store.addTab(targetTab); await store.closeLeftTabs(addTargetTab); expect(store.tabs).toHaveLength(1); expect(store.tabs[0]?.name).toBe('Contact'); }); it('closes all tabs except the specified tab', async () => { const store = useTabbarStore(); store.addTab({ fullPath: '/home', meta: {}, name: 'Home', path: '/home', } as any); const targetTab: any = { fullPath: '/about', meta: {}, name: 'About', path: '/about', }; const addTargetTab = store.addTab(targetTab); store.addTab({ fullPath: '/contact', meta: {}, name: 'Contact', path: '/contact', } as any); await store.closeOtherTabs(addTargetTab); expect(store.tabs).toHaveLength(1); expect(store.tabs[0]?.name).toBe('About'); }); it('closes all tabs to the right of the specified tab', async () => { const store = useTabbarStore(); const targetTab: any = { fullPath: '/home', meta: {}, name: 'Home', path: '/home', }; const addTargetTab = store.addTab(targetTab); store.addTab({ fullPath: '/about', meta: {}, name: 'About', path: '/about', } as any); store.addTab({ fullPath: '/contact', meta: {}, name: 'Contact', path: '/contact', } as any); await store.closeRightTabs(addTargetTab); expect(store.tabs).toHaveLength(1); expect(store.tabs[0]?.name).toBe('Home'); }); it('closes the tab with the specified key', async () => { const store = useTabbarStore(); const keyToClose = '/about'; store.addTab({ fullPath: '/home', meta: {}, name: 'Home', path: '/home', } as any); store.addTab({ fullPath: keyToClose, meta: {}, name: 'About', path: '/about', } as any); store.addTab({ fullPath: '/contact', meta: {}, name: 'Contact', path: '/contact', } as any); await store.closeTabByKey(keyToClose, router); expect(store.tabs).toHaveLength(2); expect( store.tabs.find((tab) => tab.fullPath === keyToClose), ).toBeUndefined(); }); it('refreshes the current tab', async () => { const store = useTabbarStore(); const currentTab: any = { fullPath: '/dashboard', meta: { name: 'Dashboard' }, name: 'Dashboard', path: '/dashboard', }; router.currentRoute.value = currentTab; await store.refresh(router); expect(store.excludeCachedTabs.has('Dashboard')).toBe(false); expect(store.renderRouteView).toBe(true); }); }); ================================================ FILE: packages/stores/src/modules/tabbar.ts ================================================ import type { ComputedRef } from 'vue'; import type { RouteLocationNormalized, Router, RouteRecordNormalized, } from 'vue-router'; import type { TabDefinition } from '@vben-core/typings'; import { toRaw } from 'vue'; import { preferences } from '@vben-core/preferences'; import { openRouteInNewWindow, startProgress, stopProgress, } from '@vben-core/shared/utils'; import { acceptHMRUpdate, defineStore } from 'pinia'; interface TabbarState { /** * @zh_CN 当前打开的标签页列表缓存 */ cachedTabs: Set; /** * @zh_CN 拖拽结束的索引 */ dragEndIndex: number; /** * @zh_CN 需要排除缓存的标签页 */ excludeCachedTabs: Set; /** * @zh_CN 标签右键菜单列表 */ menuList: string[]; /** * @zh_CN 是否刷新 */ renderRouteView?: boolean; /** * @zh_CN 当前打开的标签页列表 */ tabs: TabDefinition[]; /** * @zh_CN 更新时间,用于一些更新场景,使用watch深度监听的话,会损耗性能 */ updateTime?: number; } /** * @zh_CN 访问权限相关 */ export const useTabbarStore = defineStore('core-tabbar', { actions: { /** * Close tabs in bulk */ async _bulkCloseByKeys(keys: string[]) { const keySet = new Set(keys); this.tabs = this.tabs.filter( (item) => !keySet.has(getTabKeyFromTab(item)), ); await this.updateCacheTabs(); }, /** * @zh_CN 关闭标签页 * @param tab */ _close(tab: TabDefinition) { if (isAffixTab(tab)) { return; } const index = this.tabs.findIndex((item) => equalTab(item, tab)); index !== -1 && this.tabs.splice(index, 1); }, /** * @zh_CN 跳转到默认标签页 */ async _goToDefaultTab(router: Router) { if (this.getTabs.length <= 0) { return; } const firstTab = this.getTabs[0]; if (firstTab) { await this._goToTab(firstTab, router); } }, /** * @zh_CN 跳转到标签页 * @param tab * @param router */ async _goToTab(tab: TabDefinition, router: Router) { const { params, path, query } = tab; const toParams = { params: params || {}, path, query: query || {}, }; await router.replace(toParams); }, /** * @zh_CN 添加标签页 * @param routeTab */ addTab(routeTab: TabDefinition): TabDefinition { let tab = cloneTab(routeTab); if (!tab.key) { tab.key = getTabKey(routeTab); } if (!isTabShown(tab)) { return tab; } const tabIndex = this.tabs.findIndex((item) => { return equalTab(item, tab); }); if (tabIndex === -1) { const maxCount = preferences.tabbar.maxCount; // 获取动态路由打开数,超过 0 即代表需要控制打开数 const maxNumOfOpenTab = (routeTab?.meta?.maxNumOfOpenTab ?? -1) as number; // 如果动态路由层级大于 0 了,那么就要限制该路由的打开数限制了 // 获取到已经打开的动态路由数, 判断是否大于某一个值 if ( maxNumOfOpenTab > 0 && this.tabs.filter((tab) => tab.name === routeTab.name).length >= maxNumOfOpenTab ) { // 关闭第一个 const index = this.tabs.findIndex( (item) => item.name === routeTab.name, ); index !== -1 && this.tabs.splice(index, 1); } else if (maxCount > 0 && this.tabs.length >= maxCount) { // 关闭第一个 const index = this.tabs.findIndex( (item) => !Reflect.has(item.meta, 'affixTab') || !item.meta.affixTab, ); index !== -1 && this.tabs.splice(index, 1); } this.tabs.push(tab); } else { // 页面已经存在,不重复添加选项卡,只更新选项卡参数 const currentTab = toRaw(this.tabs)[tabIndex]; const mergedTab = { ...currentTab, ...tab, meta: { ...currentTab?.meta, ...tab.meta }, }; if (currentTab) { const curMeta = currentTab.meta; if (Reflect.has(curMeta, 'affixTab')) { mergedTab.meta.affixTab = curMeta.affixTab; } if (Reflect.has(curMeta, 'newTabTitle')) { mergedTab.meta.newTabTitle = curMeta.newTabTitle; } } tab = mergedTab; this.tabs.splice(tabIndex, 1, mergedTab); } this.updateCacheTabs(); return tab; }, /** * @zh_CN 关闭所有标签页 */ async closeAllTabs(router: Router) { const newTabs = this.tabs.filter((tab) => isAffixTab(tab)); this.tabs = newTabs.length > 0 ? newTabs : [...this.tabs].splice(0, 1); await this._goToDefaultTab(router); this.updateCacheTabs(); }, /** * @zh_CN 关闭左侧标签页 * @param tab */ async closeLeftTabs(tab: TabDefinition) { const index = this.tabs.findIndex((item) => equalTab(item, tab)); if (index < 1) { return; } const leftTabs = this.tabs.slice(0, index); const keys: string[] = []; for (const item of leftTabs) { if (!isAffixTab(item)) { keys.push(item.key as string); } } await this._bulkCloseByKeys(keys); }, /** * @zh_CN 关闭其他标签页 * @param tab */ async closeOtherTabs(tab: TabDefinition) { const closeKeys = this.tabs.map((item) => getTabKeyFromTab(item)); const keys: string[] = []; for (const key of closeKeys) { if (key !== getTabKeyFromTab(tab)) { const closeTab = this.tabs.find( (item) => getTabKeyFromTab(item) === key, ); if (!closeTab) { continue; } if (!isAffixTab(closeTab)) { keys.push(closeTab.key as string); } } } await this._bulkCloseByKeys(keys); }, /** * @zh_CN 关闭右侧标签页 * @param tab */ async closeRightTabs(tab: TabDefinition) { const index = this.tabs.findIndex((item) => equalTab(item, tab)); if (index !== -1 && index < this.tabs.length - 1) { const rightTabs = this.tabs.slice(index + 1); const keys: string[] = []; for (const item of rightTabs) { if (!isAffixTab(item)) { keys.push(item.key as string); } } await this._bulkCloseByKeys(keys); } }, /** * @zh_CN 关闭标签页 * @param tab * @param router */ async closeTab(tab: TabDefinition, router: Router) { const { currentRoute } = router; // 关闭不是激活选项卡 if (getTabKey(currentRoute.value) !== getTabKeyFromTab(tab)) { this._close(tab); this.updateCacheTabs(); return; } const index = this.getTabs.findIndex( (item) => getTabKeyFromTab(item) === getTabKey(currentRoute.value), ); const before = this.getTabs[index - 1]; const after = this.getTabs[index + 1]; // 下一个tab存在,跳转到下一个 if (after) { this._close(tab); await this._goToTab(after, router); // 上一个tab存在,跳转到上一个 } else if (before) { this._close(tab); await this._goToTab(before, router); } else { console.error('Failed to close the tab; only one tab remains open.'); } }, /** * @zh_CN 通过key关闭标签页 * @param key * @param router */ async closeTabByKey(key: string, router: Router) { const originKey = decodeURIComponent(key); const index = this.tabs.findIndex( (item) => getTabKeyFromTab(item) === originKey, ); if (index === -1) { return; } const tab = this.tabs[index]; if (tab) { await this.closeTab(tab, router); } }, /** * 根据tab的key获取tab * @param key */ getTabByKey(key: string) { return this.getTabs.find( (item) => getTabKeyFromTab(item) === key, ) as TabDefinition; }, /** * @zh_CN 新窗口打开标签页 * @param tab */ async openTabInNewWindow(tab: TabDefinition) { openRouteInNewWindow(tab.fullPath || tab.path); }, /** * @zh_CN 固定标签页 * @param tab */ async pinTab(tab: TabDefinition) { const index = this.tabs.findIndex((item) => equalTab(item, tab)); if (index === -1) { return; } const oldTab = this.tabs[index]; tab.meta.affixTab = true; tab.meta.title = oldTab?.meta?.title as string; // this.addTab(tab); this.tabs.splice(index, 1, tab); // 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值 const affixTabs = this.tabs.filter((tab) => isAffixTab(tab)); // 获得固定tabs的index const newIndex = affixTabs.findIndex((item) => equalTab(item, tab)); // 交换位置重新排序 await this.sortTabs(index, newIndex); }, /** * 刷新标签页 */ async refresh(router: Router | string) { // 如果是Router路由,那么就根据当前路由刷新 // 如果是string字符串,为路由名称,则定向刷新指定标签页,不能是当前路由名称,否则不会刷新 if (typeof router === 'string') { return await this.refreshByName(router); } const { currentRoute } = router; const { name } = currentRoute.value; this.excludeCachedTabs.add(name as string); this.renderRouteView = false; startProgress(); await new Promise((resolve) => setTimeout(resolve, 200)); this.excludeCachedTabs.delete(name as string); this.renderRouteView = true; stopProgress(); }, /** * 根据路由名称刷新指定标签页 */ async refreshByName(name: string) { this.excludeCachedTabs.add(name); await new Promise((resolve) => setTimeout(resolve, 200)); this.excludeCachedTabs.delete(name); }, /** * @zh_CN 重置标签页标题 */ async resetTabTitle(tab: TabDefinition) { if (tab?.meta?.newTabTitle) { return; } const findTab = this.tabs.find((item) => equalTab(item, tab)); if (findTab) { findTab.meta.newTabTitle = undefined; await this.updateCacheTabs(); } }, /** * 设置固定标签页 * @param tabs */ setAffixTabs(tabs: RouteRecordNormalized[]) { for (const tab of tabs) { tab.meta.affixTab = true; this.addTab(routeToTab(tab)); } }, /** * @zh_CN 更新菜单列表 * @param list */ setMenuList(list: string[]) { this.menuList = list; }, /** * @zh_CN 设置标签页标题 * * @zh_CN 支持设置静态标题字符串或计算属性作为动态标题 * @zh_CN 当标题为计算属性时,标题会随计算属性值变化而自动更新 * @zh_CN 适用于需要根据状态或多语言动态更新标题的场景 * * @param {TabDefinition} tab - 标签页对象 * @param {ComputedRef | string} title - 标题内容,支持静态字符串或计算属性 * * @example * // 设置静态标题 * setTabTitle(tab, '新标签页'); * * @example * // 设置动态标题 * setTabTitle(tab, computed(() => t('common.dashboard'))); */ async setTabTitle(tab: TabDefinition, title: ComputedRef | string) { const findTab = this.tabs.find((item) => equalTab(item, tab)); if (findTab) { findTab.meta.newTabTitle = title; await this.updateCacheTabs(); } }, setUpdateTime() { this.updateTime = Date.now(); }, /** * @zh_CN 设置标签页顺序 * @param oldIndex * @param newIndex */ async sortTabs(oldIndex: number, newIndex: number) { const currentTab = this.tabs[oldIndex]; if (!currentTab) { return; } this.tabs.splice(oldIndex, 1); this.tabs.splice(newIndex, 0, currentTab); this.dragEndIndex = this.dragEndIndex + 1; }, /** * @zh_CN 切换固定标签页 * @param tab */ async toggleTabPin(tab: TabDefinition) { const affixTab = tab?.meta?.affixTab ?? false; await (affixTab ? this.unpinTab(tab) : this.pinTab(tab)); }, /** * @zh_CN 取消固定标签页 * @param tab */ async unpinTab(tab: TabDefinition) { const index = this.tabs.findIndex((item) => equalTab(item, tab)); if (index === -1) { return; } const oldTab = this.tabs[index]; tab.meta.affixTab = false; tab.meta.title = oldTab?.meta?.title as string; // this.addTab(tab); this.tabs.splice(index, 1, tab); // 过滤固定tabs,后面更改affixTabOrder的值的话可能会有问题,目前行464排序affixTabs没有设置值 const affixTabs = this.tabs.filter((tab) => isAffixTab(tab)); // 获得固定tabs的index,使用固定tabs的下一个位置也就是活动tabs的第一个位置 const newIndex = affixTabs.length; // 交换位置重新排序 await this.sortTabs(index, newIndex); }, /** * 根据当前打开的选项卡更新缓存 */ async updateCacheTabs() { const cacheMap = new Set(); for (const tab of this.tabs) { // 跳过不需要持久化的标签页 const keepAlive = tab.meta?.keepAlive; if (!keepAlive) { continue; } (tab.matched || []).forEach((t, i) => { if (i > 0) { cacheMap.add(t.name as string); } }); const name = tab.name as string; cacheMap.add(name); } this.cachedTabs = cacheMap; }, }, getters: { affixTabs(): TabDefinition[] { const affixTabs = this.tabs.filter((tab) => isAffixTab(tab)); return affixTabs.sort((a, b) => { const orderA = (a.meta?.affixTabOrder ?? 0) as number; const orderB = (b.meta?.affixTabOrder ?? 0) as number; return orderA - orderB; }); }, getCachedTabs(): string[] { return [...this.cachedTabs]; }, getExcludeCachedTabs(): string[] { return [...this.excludeCachedTabs]; }, getMenuList(): string[] { return this.menuList; }, getTabs(): TabDefinition[] { const normalTabs = this.tabs.filter((tab) => !isAffixTab(tab)); return [...this.affixTabs, ...normalTabs].filter(Boolean); }, }, persist: [ // tabs不需要保存在localStorage { pick: ['tabs'], storage: sessionStorage, }, ], state: (): TabbarState => ({ cachedTabs: new Set(), dragEndIndex: 0, excludeCachedTabs: new Set(), menuList: [ 'close', 'affix', 'maximize', 'reload', 'open-in-new-window', 'close-left', 'close-right', 'close-other', 'close-all', ], renderRouteView: true, tabs: [], updateTime: Date.now(), }), }); // 解决热更新问题 const hot = import.meta.hot; if (hot) { hot.accept(acceptHMRUpdate(useTabbarStore, hot)); } /** * @zh_CN 克隆路由,防止路由被修改 * @param route */ function cloneTab(route: TabDefinition): TabDefinition { if (!route) { return route; } const { matched, meta, ...opt } = route; return { ...opt, matched: (matched ? matched.map((item) => ({ meta: item.meta, name: item.name, path: item.path, })) : undefined) as RouteRecordNormalized[], meta: { ...meta, newTabTitle: meta.newTabTitle, }, }; } /** * @zh_CN 是否是固定标签页 * @param tab */ function isAffixTab(tab: TabDefinition) { return tab?.meta?.affixTab ?? false; } /** * @zh_CN 是否显示标签 * @param tab */ function isTabShown(tab: TabDefinition) { const matched = tab?.matched ?? []; return !tab.meta.hideInTab && matched.every((item) => !item.meta.hideInTab); } /** * 从route获取tab页的key * @param tab */ function getTabKey(tab: RouteLocationNormalized | RouteRecordNormalized) { const { fullPath, path, meta: { fullPathKey } = {}, query = {}, } = tab as RouteLocationNormalized; // pageKey可能是数组(查询参数重复时可能出现) const pageKey = Array.isArray(query.pageKey) ? query.pageKey[0] : query.pageKey; let rawKey; if (pageKey) { rawKey = pageKey; } else { rawKey = fullPathKey === false ? path : (fullPath ?? path); } try { return decodeURIComponent(rawKey); } catch { return rawKey; } } /** * 从tab获取tab页的key * 如果tab没有key,那么就从route获取key * @param tab */ function getTabKeyFromTab(tab: TabDefinition): string { return tab.key ?? getTabKey(tab); } /** * 比较两个tab是否相等 * @param a * @param b */ function equalTab(a: TabDefinition, b: TabDefinition) { return getTabKeyFromTab(a) === getTabKeyFromTab(b); } function routeToTab(route: RouteRecordNormalized) { return { meta: route.meta, name: route.name, path: route.path, key: getTabKey(route), } as TabDefinition; } export { getTabKey }; ================================================ FILE: packages/stores/src/modules/user.test.ts ================================================ import { createPinia, setActivePinia } from 'pinia'; import { beforeEach, describe, expect, it } from 'vitest'; import { useUserStore } from './user'; describe('useUserStore', () => { beforeEach(() => { setActivePinia(createPinia()); }); it('returns correct userInfo', () => { const store = useUserStore(); const userInfo: any = { name: 'Jane Doe', roles: [{ value: 'user' }] }; store.setUserInfo(userInfo); expect(store.userInfo).toEqual(userInfo); }); // 测试重置用户信息时的行为 it('clears userInfo and userRoles when setting null userInfo', () => { const store = useUserStore(); store.setUserInfo({ roles: [{ roleName: 'User', value: 'user' }], } as any); expect(store.userInfo).not.toBeNull(); expect(store.userRoles.length).toBeGreaterThan(0); store.setUserInfo(null as any); expect(store.userInfo).toBeNull(); expect(store.userRoles).toEqual([]); }); // 测试在没有用户角色时返回空数组 it('returns an empty array for userRoles if not set', () => { const store = useUserStore(); expect(store.userRoles).toEqual([]); }); }); ================================================ FILE: packages/stores/src/modules/user.ts ================================================ import { acceptHMRUpdate, defineStore } from 'pinia'; interface BasicUserInfo { [key: string]: any; /** * 头像 */ avatar: string; /** * 邮箱 */ email: string; /** * 用户权限 */ permissions: string[]; /** * 用户昵称 */ realName: string; /** * 用户角色 */ roles: string[]; /** * 用户id */ userId: number | string; /** * 用户名 */ username: string; } interface AccessState { /** * 用户信息 */ userInfo: BasicUserInfo | null; /** * 用户角色 */ userRoles: string[]; } /** * @zh_CN 用户信息相关 */ export const useUserStore = defineStore('core-user', { actions: { setUserInfo(userInfo: BasicUserInfo | null) { // 设置用户信息 this.userInfo = userInfo; // 设置角色信息 const roles = userInfo?.roles ?? []; this.setUserRoles(roles); }, setUserRoles(roles: string[]) { this.userRoles = roles; }, }, state: (): AccessState => ({ userInfo: null, userRoles: [], }), }); // 解决热更新问题 const hot = import.meta.hot; if (hot) { hot.accept(acceptHMRUpdate(useUserStore, hot)); } ================================================ FILE: packages/stores/src/setup.ts ================================================ import type { Pinia } from 'pinia'; import type { App } from 'vue'; import { createPinia } from 'pinia'; import SecureLS from 'secure-ls'; let pinia: Pinia; export interface InitStoreOptions { /** * @zh_CN 应用名,由于 @vben/stores 是公用的,后续可能有多个app,为了防止多个app缓存冲突,可在这里配置应用名,应用名将被用于持久化的前缀 */ namespace: string; } /** * @zh_CN 初始化pinia */ export async function initStores(app: App, options: InitStoreOptions) { const { createPersistedState } = await import('pinia-plugin-persistedstate'); pinia = createPinia(); const { namespace } = options; const ls = new SecureLS({ encodingType: 'aes', encryptionSecret: import.meta.env.VITE_APP_STORE_SECURE_KEY, isCompression: true, // @ts-ignore secure-ls does not have a type definition for this metaKey: `${namespace}-secure-meta`, }); pinia.use( createPersistedState({ // key $appName-$store.id key: (storeKey) => `${namespace}-${storeKey}`, storage: import.meta.env.DEV ? localStorage : { getItem(key) { return ls.get(key); }, setItem(key, value) { ls.set(key, value); }, }, }), ); app.use(pinia); return pinia; } export function resetAllStores() { if (!pinia) { console.error('Pinia is not installed'); return; } const allStores = (pinia as any)._s; for (const [_key, store] of allStores) { store.$reset(); } } ================================================ FILE: packages/stores/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src", "shim-pinia.d.ts"] } ================================================ FILE: packages/styles/README.md ================================================ # @vben/styles 用于多个 `app` 公用的样式文件,继承了 `@vben-core/design` 的所有能力。业务上有通用的样式文件可以放在这里。 ## 用法 ### 添加依赖 ```bash # 进入目标应用目录,例如 apps/xxxx-app # cd apps/xxxx-app pnpm add @vben/styles ``` ### 使用 ```ts import '@vben/styles'; ``` ================================================ FILE: packages/styles/package.json ================================================ { "name": "@vben/styles", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/styles" }, "license": "MIT", "type": "module", "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" }, "./antd": { "default": "./src/antd/index.css" }, "./ele": { "default": "./src/ele/index.css" }, "./naive": { "default": "./src/naive/index.css" }, "./global": { "default": "./src/global/index.scss" } }, "dependencies": { "@vben-core/design": "workspace:*" } } ================================================ FILE: packages/styles/src/antd/index.css ================================================ /* ant-design-vue 组件库的一些样式重置 */ .ant-app { width: 100%; height: 100%; overscroll-behavior: none; color: inherit; } .ant-btn { .anticon { display: inline-flex; } /* * 修复按钮添加图标时的位置问题 */ > svg { display: inline-block; } > svg + span { margin-inline-start: 6px; } } .ant-tag { > svg { display: inline-block; } > svg + span { margin-inline-start: 4px; } } .ant-message-notice-content, .ant-notification-notice { @apply dark:border-border/60 dark:border; } .form-valid-error { /** select 选择器的样式 */ .ant-select:not(.valid-success) .ant-select-selector:not(.valid-success) { border-color: hsl(var(--destructive)) !important; } .ant-select-focused .ant-select-selector { box-shadow: 0 0 0 2px rgb(255 38 5 / 6%) !important; } /** 数字输入框样式 */ .ant-input-number-focused { box-shadow: 0 0 0 2px rgb(255 38 5 / 6%); } /** 密码输入框样式 */ .ant-input-affix-wrapper:hover { border-color: hsl(var(--destructive)); box-shadow: 0 0 0 2px rgb(255 38 5 / 6%); } .ant-input:not(.valid-success) { border-color: hsl(var(--destructive)) !important; } } /** 区间选择器下面来回切换时的样式 */ .form-valid-error .ant-picker-active-bar { background-color: hsl(var(--destructive)); } /** 时间选择器的样式 */ .form-valid-error .ant-picker-focused { box-shadow: 0 0 0 2px rgb(255 38 5 / 6%); } /** 前后置小圆点样式 */ .dot-before-common { @apply before:relative before:top-[-2px] before:mr-[5px] before:inline-block before:size-[6px] before:rounded-full before:content-['']; } .dot-before-red { @apply dot-before-common before:bg-red-500; } .dot-before-green { @apply dot-before-common before:bg-green-500; } /** vxe表格右上角toolbar元素之间的间距 */ .vxe-button + .vxe-button.type--button { margin-left: 8px !important; } /** vxe默认圆角 */ html { --vxe-ui-border-radius: 8px !important; } /** vxe表格loading 只加载表格 不加载上面的表单 */ .vxe-grid.is--loading::before { content: none !important; } /** 自定义success按钮样式 ghost按钮专用! */ .btn-success { color: hsl(var(--success)) !important; border-color: hsl(var(--success)) !important; } .btn-success:hover { color: hsl(var(--success) / 50%) !important; border-color: hsl(var(--success) / 50%) !important; } html.dark button[disabled].btn-success { color: rgb(242 242 242 / 25%) !important; border-color: hsl(240deg 3.7% 22%) !important; } button[disabled].btn-success { color: rgb(50 54 57 / 25%) !important; border-color: hsl(240deg 5.9% 90%) !important; } ================================================ FILE: packages/styles/src/ele/index.css ================================================ .el-card { --el-card-border-radius: var(--radius) !important; } .form-valid-error { /** select 选择器的样式 */ .el-select .el-select__wrapper { box-shadow: 0 0 0 1px var(--el-color-danger) inset; } /** input 选择器的样式 */ .el-input .el-input__wrapper { box-shadow: 0 0 0 1px var(--el-color-danger) inset; } /** radio和checkbox 选择器的样式 */ .el-radio .el-radio__inner, .el-checkbox .el-checkbox__inner { border: 1px solid var(--el-color-danger); } .el-checkbox-button .el-checkbox-button__inner, .el-radio-button .el-radio-button__inner { border: 1px solid var(--el-color-danger); } .el-checkbox-button:first-child .el-checkbox-button__inner, .el-radio-button:first-child .el-radio-button__inner { border-left: 1px solid var(--el-color-danger); } .el-checkbox-button:not(:first-child) .el-checkbox-button__inner, .el-radio-button:not(:first-child) .el-radio-button__inner { border-left: none; } .el-textarea .el-textarea__inner { border: 1px solid var(--el-color-danger); } } html .el-loading-mask { z-index: 1000; } ================================================ FILE: packages/styles/src/global/index.scss ================================================ @use '@vben-core/design/bem' as *; ================================================ FILE: packages/styles/src/index.ts ================================================ import '@vben-core/design'; ================================================ FILE: packages/styles/src/naive/index.css ================================================ .form-valid-error { .n-base-selection__state-border, .n-input__state-border, .n-radio-group__splitor { border: var(--n-border-error); } .n-radio-group .n-radio-button, .n-radio-group .n-radio-group__splitor { --n-button-border-color: rgb(255 56 96); } .n-radio__dot { --n-box-shadow: inset 0 0 0 1px rgb(255 56 96); } .n-checkbox-box__border { --n-border: 1px solid rgb(255 56 96); } } ================================================ FILE: packages/styles/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/types/README.md ================================================ # @vben/types 用于多个 `app` 公用的工具类型,继承了 `@vben-core/typings` 的所有能力。业务上有通用的类型定义可以放在这里。 ## 用法 ### 添加依赖 ```bash # 进入目标应用目录,例如 apps/xxxx-app # cd apps/xxxx-app pnpm add @vben/types ``` ### 使用 ```ts // 推荐加上 type import type { SelectOption } from '@vben/types'; ``` ================================================ FILE: packages/types/global.d.ts ================================================ import type { RouteMeta as IRouteMeta } from '@vben-core/typings'; import 'vue-router'; declare module 'vue-router' { // eslint-disable-next-line @typescript-eslint/no-empty-object-type interface RouteMeta extends IRouteMeta {} } export interface VbenAdminProAppConfigRaw { // 后端接口地址 VITE_GLOB_API_URL: string; // 客户端ID VITE_GLOB_APP_CLIENT_ID: string; // # 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) VITE_GLOB_ENABLE_ENCRYPT: string; // RSA请求解密私钥 VITE_GLOB_RSA_PRIVATE_KEY: string; // RSA请求加密公钥 VITE_GLOB_RSA_PUBLIC_KEY: string; // 是否开启sse 注意从配置文件获取的类型为string VITE_GLOB_SSE_ENABLE: string; // 开启websocket 注意从配置文件获取的类型为string VITE_GLOB_WEBSOCKET_ENABLE: string; } export interface ApplicationConfig { // 后端接口地址 apiURL: string; // 客户端key clientId: string; // 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应) enableEncrypt: boolean; // RSA响应解密私钥 rsaPrivateKey: string; // RSA请求加密公钥 rsaPublicKey: string; // 是否开启sse sseEnable: boolean; // 是否开启 websocketEnable: boolean; } declare global { interface Window { _VBEN_ADMIN_PRO_APP_CONF_: VbenAdminProAppConfigRaw; } } ================================================ FILE: packages/types/package.json ================================================ { "name": "@vben/types", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/types" }, "license": "MIT", "type": "module", "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" }, "./global": { "types": "./global.d.ts" } }, "dependencies": { "@vben-core/typings": "workspace:*", "vue": "catalog:", "vue-router": "catalog:" } } ================================================ FILE: packages/types/src/index.ts ================================================ export type * from './user'; export type * from '@vben-core/typings'; ================================================ FILE: packages/types/src/user.ts ================================================ import type { BasicUserInfo } from '@vben-core/typings'; /** 用户信息 */ interface UserInfo extends BasicUserInfo { /** * 拓展使用 */ [key: string]: any; } export type { UserInfo }; ================================================ FILE: packages/types/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/web.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: packages/utils/README.md ================================================ # @vben/utils 用于多个 `app` 公用的工具包,继承了 `@vben-core/shared/utils` 的所有能力。业务上有通用的工具函数可以放在这里。 ## 用法 ### 添加依赖 ```bash # 进入目标应用目录,例如 apps/xxxx-app # cd apps/xxxx-app pnpm add @vben/utils ``` ### 使用 ```ts import { isString } from '@vben/utils'; ``` ================================================ FILE: packages/utils/package.json ================================================ { "name": "@vben/utils", "version": "5.5.9", "homepage": "https://github.com/vbenjs/vue-vben-admin", "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", "repository": { "type": "git", "url": "git+https://github.com/vbenjs/vue-vben-admin.git", "directory": "packages/utils" }, "license": "MIT", "type": "module", "sideEffects": [ "**/*.css" ], "exports": { ".": { "types": "./src/index.ts", "default": "./src/index.ts" } }, "dependencies": { "@vben-core/shared": "workspace:*", "@vben-core/typings": "workspace:*", "crypto-js": "^4.2.0", "file-type": "^19.5.0", "jsencrypt": "^3.5.4", "sm-crypto": "^0.3.13", "vue-router": "catalog:" }, "devDependencies": { "@types/crypto-js": "^4.2.2", "@types/sm-crypto": "^0.3.4" } } ================================================ FILE: packages/utils/src/encryption/base.ts ================================================ export interface EncryptionOptions { /** * 私钥 */ privateKey: string; /** * 公钥 */ publicKey: string; } /** * 非对称加解密 抽象类 * 提供基本的加密和解密功能接口 */ export abstract class BaseAsymmetricEncryption { /** * 私钥 */ protected privateKey: string; /** * 公钥 */ protected publicKey: string; /** * 构造函数 * @param options 加解密选项,包含公钥和私钥 */ constructor(options: EncryptionOptions) { this.publicKey = options.publicKey; this.privateKey = options.privateKey; } /** * 解密方法 * @param encryptedData 解密后的数据 * @returns 解密后的原始数据 */ abstract decrypt(encryptedData: string): string; /** * 加密方法 * @param data 需要加密的数据 * @returns 加密后的数据 */ abstract encrypt(data: string): string; } /** * 对称加解密抽象类 */ export abstract class BaseSymmetricEncryption { /** * 解密方法 * @param data 解密后的数据 * @param key 密钥 * @returns 解密后的原始数据 */ abstract decrypt(data: string, key: string): string; /** * 加密方法 * @param data 需要加密的数据 * @param key 密钥 * @returns 加密后的数据 */ abstract encrypt(data: string, key: string): string; } ================================================ FILE: packages/utils/src/encryption/crypto.ts ================================================ import CryptoJS from 'crypto-js'; /** * 随机字符串 * * @returns str */ export function randomStr(length = 32) { const str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; let result = ''; for (let i = length; i > 0; --i) result += str[Math.floor(Math.random() * str.length)]; return result; } /** * base64编码 * @param str * @returns base64编码 */ export function encodeBase64(str: string) { return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(str)); } /** * 解码base64 */ export function decodeBase64(str: string) { return CryptoJS.enc.Base64.parse(str).toString(CryptoJS.enc.Utf8); } ================================================ FILE: packages/utils/src/encryption/impl/aes.ts ================================================ import CryptoJS from 'crypto-js'; import { BaseSymmetricEncryption } from '../base'; /** * AES 实现 */ export class AesEncryption extends BaseSymmetricEncryption { override decrypt(data: string, key: string): string { // 必须格式化字符串才能正常使用 const aesKey = CryptoJS.enc.Utf8.parse(key); const decrypted = CryptoJS.AES.decrypt(data, aesKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7, }); return decrypted.toString(CryptoJS.enc.Utf8); } override encrypt(data: string, key: string): string { // 必须格式化字符串才能正常使用 const aesKey = CryptoJS.enc.Utf8.parse(key); const encrypted = CryptoJS.AES.encrypt(data, aesKey, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7, }); return encrypted.toString(); } } ================================================ FILE: packages/utils/src/encryption/impl/rsa.ts ================================================ import JSEncrypt from 'jsencrypt'; import { BaseAsymmetricEncryption } from '../base'; /** * RSA 实现 */ export class RsaEncryption extends BaseAsymmetricEncryption { override decrypt(str: string): string { const instance = new JSEncrypt(); instance.setPrivateKey(this.privateKey); const ret = instance.decrypt(str); if (ret === false) { throw new Error('RsaEncryption decrypt error'); } return ret; } override encrypt(str: string): string { const instance = new JSEncrypt(); instance.setPublicKey(this.publicKey); const ret = instance.encrypt(str); if (ret === false) { throw new Error('RsaEncryption encrypt error'); } return ret; } } ================================================ FILE: packages/utils/src/encryption/impl/sm2.ts ================================================ /* eslint-disable prefer-template */ /* eslint-disable no-console */ import { sm2 } from 'sm-crypto'; import { BaseAsymmetricEncryption } from '../base'; /** * SM2 实现 * 注意生成的公钥必须为04开头 或者使用下面的generateSm2KeyPair生成 * @see https://tool.hiofd.com/sm2-key-gen/ 这里可以生成04开头的SM2密钥对 */ export class Sm2Encryption extends BaseAsymmetricEncryption { override decrypt(hexStr: string): string { /** * 后端必须使用`EncryptUtils.encryptBySm2Hex`来加密而不是base64 * 后端返回会固定带04前缀 需要去除 * * @see https://github.com/JuneAndGreen/sm-crypto?tab=readme-ov-file#%E5%8A%A0%E5%AF%86%E8%A7%A3%E5%AF%86 * ps:密文会在解密时自动补充 04,如遇到其他工具补充的 04 需手动去除再传入。 */ if (hexStr.startsWith('04')) { hexStr = hexStr.slice(2); } return sm2.doDecrypt(hexStr, this.privateKey); } override encrypt(str: string): string { /** * sm2解密有千分之几的错误,报异常java.lang.IllegalArgumentException: Invalid point coordinates * @see https://github.com/chinabugotech/hutool/issues/3262 * * 固定加上04前缀 避免出现上述问题 */ return '04' + sm2.doEncrypt(str, this.publicKey); } } export function generateSm2KeyPair() { const { privateKey, publicKey } = sm2.generateKeyPairHex(); return { privateKey, publicKey, }; } export function logSm2KeyPair() { const { privateKey, publicKey } = generateSm2KeyPair(); console.log('privateKey', privateKey); console.log('publicKey', publicKey); } ================================================ FILE: packages/utils/src/encryption/impl/sm4.ts ================================================ import CryptoJS from 'crypto-js'; import { sm4 } from 'sm-crypto'; import { BaseSymmetricEncryption } from '../base'; /** * SM4 实现 */ export class Sm4Encryption extends BaseSymmetricEncryption { /** * 解密 data必须为hex字符串 可使用后端EncryptUtils.encryptBySm4Hex来加密 * @param hexString 待解密数据 只接受hex类型的字符串 * @param key 秘钥 * @returns result */ override decrypt(hexString: string, key: string): string { this.checkKey(key); const keyHex = CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(key)); return sm4.decrypt(hexString, keyHex); } override encrypt(data: string, key: string): string { this.checkKey(key); /** * 转hex字符串 * encrypt方法的key需要为`16进制字符串`而非`原始字符串` * 比如字符串ab a为0x61 b为0x62 转字符串为 6162 */ const keyHex = CryptoJS.enc.Hex.stringify(CryptoJS.enc.Utf8.parse(key)); return sm4.encrypt(data, keyHex); } /** * key长度只能为16位字符串 * @param key key */ private checkKey(key: string) { if (key.length !== 16) { throw new Error('SM4 key must be 16 bytes'); } } } ================================================ FILE: packages/utils/src/encryption/index.ts ================================================ export * from './base'; export * from './crypto'; export * from './impl/aes'; export * from './impl/rsa'; export * from './impl/sm2'; export * from './impl/sm4'; ================================================ FILE: packages/utils/src/helpers/__tests__/enum-options.test.ts ================================================ import { describe, expect, it } from 'vitest'; import { optionsToEnum } from '../enum-options'; describe('optionsToEnum Test', () => { it('should return an enum object', () => { const genderOptions = [ { label: '男', value: 1, enumName: 'GENDER_MALE' }, { label: '女', value: 2, enumName: 'GENDER_FEMALE' }, ] as const; const enumTest = optionsToEnum(genderOptions); const male = enumTest.GENDER_MALE; const female = enumTest.GENDER_FEMALE; expect(male).toBe(1); expect(female).toBe(2); }); }); ================================================ FILE: packages/utils/src/helpers/__tests__/find-menu-by-path.test.ts ================================================ import { describe, expect, it } from 'vitest'; import { findMenuByPath, findRootMenuByPath } from '../find-menu-by-path'; // 示例菜单数据 const menus: any[] = [ { path: '/', children: [] }, { path: '/about', children: [] }, { path: '/contact', children: [ { path: '/contact/email', children: [] }, { path: '/contact/phone', children: [] }, ], }, { path: '/services', children: [ { path: '/services/design', children: [] }, { path: '/services/development', children: [{ path: '/services/development/web', children: [] }], }, ], }, ]; describe('menu Finder Tests', () => { it('finds a top-level menu', () => { const menu = findMenuByPath(menus, '/about'); expect(menu).toBeDefined(); expect(menu?.path).toBe('/about'); }); it('finds a nested menu', () => { const menu = findMenuByPath(menus, '/services/development/web'); expect(menu).toBeDefined(); expect(menu?.path).toBe('/services/development/web'); }); it('returns null for a non-existent path', () => { const menu = findMenuByPath(menus, '/non-existent'); expect(menu).toBeNull(); }); it('handles empty menus list', () => { const menu = findMenuByPath([], '/about'); expect(menu).toBeNull(); }); it('handles menu items without children', () => { const menu = findMenuByPath( [{ path: '/only', children: undefined }] as any[], '/only', ); expect(menu).toBeDefined(); expect(menu?.path).toBe('/only'); }); it('finds root menu by path', () => { const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath( menus, '/services/development/web', ); expect(findMenu).toBeDefined(); expect(rootMenu).toBeUndefined(); expect(rootMenuPath).toBeUndefined(); expect(findMenu?.path).toBe('/services/development/web'); }); it('returns null for undefined or empty path', () => { const menuUndefinedPath = findMenuByPath(menus); const menuEmptyPath = findMenuByPath(menus, ''); expect(menuUndefinedPath).toBeNull(); expect(menuEmptyPath).toBeNull(); }); it('checks for root menu when path does not exist', () => { const { findMenu, rootMenu, rootMenuPath } = findRootMenuByPath( menus, '/non-existent', ); expect(findMenu).toBeNull(); expect(rootMenu).toBeUndefined(); expect(rootMenuPath).toBeUndefined(); }); }); ================================================ FILE: packages/utils/src/helpers/__tests__/generate-menus.test.ts ================================================ import type { Router, RouteRecordRaw } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router'; import { describe, expect, it, vi } from 'vitest'; import { generateMenus } from '../generate-menus'; // Nested route setup to test child inclusion and hideChildrenInMenu functionality describe('generateMenus', () => { // 模拟路由数据 const mockRoutes = [ { meta: { icon: 'home-icon', title: '首页' }, name: 'home', path: '/home', }, { meta: { hideChildrenInMenu: true, icon: 'about-icon', title: '关于' }, name: 'about', path: '/about', children: [ { path: 'team', name: 'team', meta: { icon: 'team-icon', title: '团队' }, }, ], }, ] as RouteRecordRaw[]; // 模拟 Vue 路由器实例 const mockRouter = { getRoutes: vi.fn(() => [ { name: 'home', path: '/home' }, { name: 'about', path: '/about' }, { name: 'team', path: '/about/team' }, ]), }; it('the correct menu list should be generated according to the route', async () => { const expectedMenus = [ { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: 'home-icon', name: '首页', order: undefined, parent: undefined, parents: undefined, path: '/home', show: true, children: [], }, { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: 'about-icon', name: '关于', order: undefined, parent: undefined, parents: undefined, path: '/about', show: true, children: [], }, ]; const menus = generateMenus(mockRoutes, mockRouter as any); expect(menus).toEqual(expectedMenus); }); it('includes additional meta properties in menu items', async () => { const mockRoutesWithMeta = [ { meta: { icon: 'user-icon', order: 1, title: 'Profile' }, name: 'profile', path: '/profile', }, ] as RouteRecordRaw[]; const menus = generateMenus(mockRoutesWithMeta, mockRouter as any); expect(menus).toEqual([ { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: 'user-icon', name: 'Profile', order: 1, parent: undefined, parents: undefined, path: '/profile', show: true, children: [], }, ]); }); it('handles dynamic route parameters correctly', async () => { const mockRoutesWithParams = [ { meta: { icon: 'details-icon', title: 'User Details' }, name: 'userDetails', path: '/users/:userId', }, ] as RouteRecordRaw[]; const menus = generateMenus(mockRoutesWithParams, mockRouter as any); expect(menus).toEqual([ { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: 'details-icon', name: 'User Details', order: undefined, parent: undefined, parents: undefined, path: '/users/:userId', show: true, children: [], }, ]); }); it('processes routes with redirects correctly', async () => { const mockRoutesWithRedirect = [ { name: 'redirectedRoute', path: '/old-path', redirect: '/new-path', }, { meta: { icon: 'path-icon', title: 'New Path' }, name: 'newPath', path: '/new-path', }, ] as RouteRecordRaw[]; const menus = generateMenus(mockRoutesWithRedirect, mockRouter as any); expect(menus).toEqual([ // Assuming your generateMenus function excludes redirect routes from the menu { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: undefined, name: 'redirectedRoute', order: undefined, parent: undefined, parents: undefined, path: '/old-path', show: true, children: [], }, { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: 'path-icon', name: 'New Path', order: undefined, parent: undefined, parents: undefined, path: '/new-path', show: true, children: [], }, ]); }); const routes: any = [ { meta: { order: 2, title: 'Home' }, name: 'home', path: '/', }, { meta: { order: 1, title: 'About' }, name: 'about', path: '/about', }, ]; const router: Router = createRouter({ history: createWebHistory(), routes, }); it('should generate menu list with correct order', async () => { const menus = generateMenus(routes, router); const expectedMenus = [ { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: undefined, name: 'About', order: 1, parent: undefined, parents: undefined, path: '/about', show: true, children: [], }, { badge: undefined, badgeType: undefined, badgeVariants: undefined, icon: undefined, name: 'Home', order: 2, parent: undefined, parents: undefined, path: '/', show: true, children: [], }, ]; expect(menus).toEqual(expectedMenus); }); it('should handle empty routes', async () => { const emptyRoutes: any[] = []; const menus = generateMenus(emptyRoutes, router); expect(menus).toEqual([]); }); }); ================================================ FILE: packages/utils/src/helpers/__tests__/generate-routes-frontend.test.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import { describe, expect, it } from 'vitest'; import { generateRoutesByFrontend, hasAuthority, } from '../generate-routes-frontend'; // Mock 路由数据 const mockRoutes = [ { meta: { authority: ['admin', 'user'], hideInMenu: false, }, path: '/dashboard', children: [ { path: '/dashboard/overview', meta: { authority: ['admin'], hideInMenu: false }, }, { path: '/dashboard/stats', meta: { authority: ['user'], hideInMenu: true }, }, ], }, { meta: { authority: ['admin'], hideInMenu: false }, path: '/settings', }, { meta: { hideInMenu: false }, path: '/profile', }, ] as RouteRecordRaw[]; describe('hasAuthority', () => { it('should return true if there is no authority defined', () => { expect(hasAuthority(mockRoutes[2], ['admin'])).toBe(true); }); it('should return true if the user has the required authority', () => { expect(hasAuthority(mockRoutes[0], ['admin'])).toBe(true); }); it('should return false if the user does not have the required authority', () => { expect(hasAuthority(mockRoutes[1], ['user'])).toBe(false); }); }); describe('generateRoutesByFrontend', () => { it('should handle routes without children', async () => { const generatedRoutes = await generateRoutesByFrontend(mockRoutes, [ 'user', ]); expect(generatedRoutes).toEqual( expect.arrayContaining([ expect.objectContaining({ path: '/profile', // This route has no children and should be included }), ]), ); }); it('should handle empty roles array', async () => { const generatedRoutes = await generateRoutesByFrontend(mockRoutes, []); expect(generatedRoutes).toEqual( expect.arrayContaining([ // Only routes without authority should be included expect.objectContaining({ path: '/profile', }), ]), ); expect(generatedRoutes).not.toEqual( expect.arrayContaining([ expect.objectContaining({ path: '/dashboard', }), expect.objectContaining({ path: '/settings', }), ]), ); }); it('should handle missing meta fields', async () => { const routesWithMissingMeta = [ { path: '/path1' }, // No meta { meta: {}, path: '/path2' }, // Empty meta { meta: { authority: ['admin'] }, path: '/path3' }, // Only authority ]; const generatedRoutes = await generateRoutesByFrontend( routesWithMissingMeta as RouteRecordRaw[], ['admin'], ); expect(generatedRoutes).toEqual([ { path: '/path1' }, { meta: {}, path: '/path2' }, { meta: { authority: ['admin'] }, path: '/path3' }, ]); }); }); ================================================ FILE: packages/utils/src/helpers/__tests__/merge-route-modules.test.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import type { RouteModuleType } from '../merge-route-modules'; import { describe, expect, it } from 'vitest'; import { mergeRouteModules } from '../merge-route-modules'; describe('mergeRouteModules', () => { it('should merge route modules correctly', () => { const routeModules: Record = { './dynamic-routes/about.ts': { default: [ { component: () => Promise.resolve({ template: '
About
' }), name: 'About', path: '/about', }, ], }, './dynamic-routes/home.ts': { default: [ { component: () => Promise.resolve({ template: '
Home
' }), name: 'Home', path: '/', }, ], }, }; const expectedRoutes: RouteRecordRaw[] = [ { component: expect.any(Function), name: 'About', path: '/about', }, { component: expect.any(Function), name: 'Home', path: '/', }, ]; const mergedRoutes = mergeRouteModules(routeModules); expect(mergedRoutes).toEqual(expectedRoutes); }); it('should handle empty modules', () => { const routeModules: Record = {}; const expectedRoutes: RouteRecordRaw[] = []; const mergedRoutes = mergeRouteModules(routeModules); expect(mergedRoutes).toEqual(expectedRoutes); }); it('should handle modules with no default export', () => { const routeModules: Record = { './dynamic-routes/empty.ts': { default: [], }, }; const expectedRoutes: RouteRecordRaw[] = []; const mergedRoutes = mergeRouteModules(routeModules); expect(mergedRoutes).toEqual(expectedRoutes); }); }); ================================================ FILE: packages/utils/src/helpers/enum-options.ts ================================================ /** * @author dap * @description 枚举选项 */ /** * 定义options类型 */ export interface EnumsOption { /** * 枚举名称 建议使用全大写字母_ */ enumName: string; /** * option的标签 */ label: string; /** * option的值 */ value: boolean | number | string; } export type EnumResult = { [key in T[number]['enumName']]: Extract< T[number], { enumName: key } >['value']; }; /** * 将options转为枚举 * 注意自定义的options需要加上as const作为常量处理 * 详见: packages\utils\src\helpers\__tests__\enum-options.test.ts * @param options 枚举选项 * @returns 转枚举 */ export function optionsToEnum( options: T, ): EnumResult { type K = T[number]['enumName']; const result = {} as EnumResult; options.forEach((item) => { result[item.enumName as K] = item.value; }); return result; } ================================================ FILE: packages/utils/src/helpers/find-menu-by-path.ts ================================================ import type { MenuRecordRaw } from '@vben-core/typings'; function findMenuByPath( list: MenuRecordRaw[], path?: string, ): MenuRecordRaw | null { for (const menu of list) { if (menu.path === path) { return menu; } const findMenu = menu.children && findMenuByPath(menu.children, path); if (findMenu) { return findMenu; } } return null; } /** * 查找根菜单 * @param menus * @param path */ function findRootMenuByPath(menus: MenuRecordRaw[], path?: string, level = 0) { const findMenu = findMenuByPath(menus, path); const rootMenuPath = findMenu?.parents?.[level]; const rootMenu = rootMenuPath ? menus.find((item) => item.path === rootMenuPath) : undefined; return { findMenu, rootMenu, rootMenuPath, }; } export { findMenuByPath, findRootMenuByPath }; ================================================ FILE: packages/utils/src/helpers/generate-menus.ts ================================================ import type { Router, RouteRecordRaw } from 'vue-router'; import type { ExRouteRecordRaw, MenuRecordRaw, RouteMeta, } from '@vben-core/typings'; import { filterTree, mapTree } from '@vben-core/shared/utils'; /** * 根据 routes 生成菜单列表 * @param routes - 路由配置列表 * @param router - Vue Router 实例 * @returns 生成的菜单列表 */ function generateMenus( routes: RouteRecordRaw[], router: Router, ): MenuRecordRaw[] { // 将路由列表转换为一个以 name 为键的对象映射 const finalRoutesMap: { [key: string]: string } = Object.fromEntries( router.getRoutes().map(({ name, path }) => [name, path]), ); let menus = mapTree(routes, (route) => { // 获取最终的路由路径 const path = finalRoutesMap[route.name as string] ?? route.path ?? ''; const { meta = {} as RouteMeta, name: routeName, redirect, children = [], } = route; const { activeIcon, badge, badgeType, badgeVariants, hideChildrenInMenu = false, icon, link, order, title = '', } = meta; // 确保菜单名称不为空 const name = (title || routeName || '') as string; // 处理子菜单 const resultChildren = hideChildrenInMenu ? [] : ((children as MenuRecordRaw[]) ?? []); // 设置子菜单的父子关系 if (resultChildren.length > 0) { resultChildren.forEach((child) => { child.parents = [...(route.parents ?? []), path]; child.parent = path; }); } // 确定最终路径 const resultPath = hideChildrenInMenu ? redirect || path : link || path; return { activeIcon, badge, badgeType, badgeVariants, icon, name, order, parent: route.parent, parents: route.parents, path: resultPath, show: !meta.hideInMenu, children: resultChildren, }; }); // 对菜单进行排序,避免order=0时被替换成999的问题 menus = menus.sort((a, b) => (a?.order ?? 999) - (b?.order ?? 999)); // 过滤掉隐藏的菜单项 return filterTree(menus, (menu) => !!menu.show); } export { generateMenus }; ================================================ FILE: packages/utils/src/helpers/generate-routes-backend.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import type { ComponentRecordType, GenerateMenuAndRoutesOptions, RouteRecordStringComponent, } from '@vben-core/typings'; import { mapTree } from '@vben-core/shared/utils'; /** * 动态生成路由 - 后端方式 */ async function generateRoutesByBackend( options: GenerateMenuAndRoutesOptions, ): Promise { const { fetchMenuListAsync, layoutMap = {}, pageMap = {} } = options; try { const menuRoutes = await fetchMenuListAsync?.(); if (!menuRoutes) { return []; } const normalizePageMap: ComponentRecordType = {}; for (const [key, value] of Object.entries(pageMap)) { normalizePageMap[normalizeViewPath(key)] = value; } const routes = convertRoutes(menuRoutes, layoutMap, normalizePageMap); return routes; } catch (error) { console.error(error); throw error; } } function convertRoutes( routes: RouteRecordStringComponent[], layoutMap: ComponentRecordType, pageMap: ComponentRecordType, ): RouteRecordRaw[] { return mapTree(routes, (node) => { const route = node as unknown as RouteRecordRaw; const { component, name } = node; if (!name) { console.error('route name is required', route); } // layout转换 if (component && layoutMap[component]) { route.component = layoutMap[component]; // 页面组件转换 } else if (component) { const normalizePath = normalizeViewPath(component); const pageKey = normalizePath.endsWith('.vue') ? normalizePath : `${normalizePath}.vue`; if (pageMap[pageKey]) { route.component = pageMap[pageKey]; } else { // console.error(`route component is invalid: ${pageKey}`, route); // route.component = pageMap['/_core/fallback/not-found.vue']; console.error(`未找到对应组件: /views${component}.vue`); // 默认为404页面 route.component = layoutMap.NotFoundComponent; } } return route; }); } function normalizeViewPath(path: string): string { // 去除相对路径前缀 const normalizedPath = path.replace(/^(\.\/|\.\.\/)+/, ''); // 确保路径以 '/' 开头 const viewPath = normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`; // 这里耦合了vben-admin的目录结构 return viewPath.replace(/^\/views/, ''); } export { generateRoutesByBackend }; ================================================ FILE: packages/utils/src/helpers/generate-routes-frontend.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; import { filterTree, mapTree } from '@vben-core/shared/utils'; /** * 动态生成路由 - 前端方式 */ async function generateRoutesByFrontend( routes: RouteRecordRaw[], roles: string[], forbiddenComponent?: RouteRecordRaw['component'], ): Promise { // 根据角色标识过滤路由表,判断当前用户是否拥有指定权限 const finalRoutes = filterTree(routes, (route) => { return hasAuthority(route, roles); }); if (!forbiddenComponent) { return finalRoutes; } // 如果有禁止访问的页面,将禁止访问的页面替换为403页面 return mapTree(finalRoutes, (route) => { if (menuHasVisibleWithForbidden(route)) { route.component = forbiddenComponent; } return route; }); } /** * 判断路由是否有权限访问 * @param route * @param access */ function hasAuthority(route: RouteRecordRaw, access: string[]) { const authority = route.meta?.authority; if (!authority) { return true; } const canAccess = access.some((value) => authority.includes(value)); return canAccess || (!canAccess && menuHasVisibleWithForbidden(route)); } /** * 判断路由是否在菜单中显示,但是访问会被重定向到403 * @param route */ function menuHasVisibleWithForbidden(route: RouteRecordRaw) { return ( !!route.meta?.authority && Reflect.has(route.meta || {}, 'menuVisibleWithForbidden') && !!route.meta?.menuVisibleWithForbidden ); } export { generateRoutesByFrontend, hasAuthority }; ================================================ FILE: packages/utils/src/helpers/get-popup-container.ts ================================================ /** * If the node is holding inside a form, return the form element, * otherwise return the parent node of the given element or * the document body if the element is not provided. */ export function getPopupContainer(node?: HTMLElement): HTMLElement { return ( node?.closest('form') ?? (node?.parentNode as HTMLElement) ?? document.body ); } /** * VxeTable专用弹窗层 * 解决问题: https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IB1DM3 * @param node 触发的元素 * @param tableId 表格ID,用于区分不同表格(可选) * @returns 挂载节点 */ export function getVxePopupContainer( node?: HTMLElement, tableId?: string, ): HTMLElement { if (!node) return document.body; // 检查是否在固定列内 const isInFixedColumn = node.closest('.vxe-table--fixed-wrapper') || node.closest('.vxe-table--fixed-left-wrapper') || node.closest('.vxe-table--fixed-right-wrapper'); // 如果在固定列内,则挂载到固定列容器 if (isInFixedColumn) { // 优先查找表格容器及父级容器 const tableContainer = // 查找通用固定列容器 node.closest('.vxe-table--fixed-wrapper') || // 查找固定列容器(左侧固定列) node.closest('.vxe-table--fixed-left-wrapper') || // 查找固定列容器(右侧固定列) node.closest('.vxe-table--fixed-right-wrapper'); // 如果指定了tableId,可以查找特定ID的表格 if (tableId && tableContainer) { const specificTable = tableContainer.closest( `[data-table-id="${tableId}"]`, ); if (specificTable) { return specificTable as HTMLElement; } } return tableContainer as HTMLElement; } /** * 设置行高度需要特殊处理 */ const fixedHeightElement = node.closest('td.col--cs-height'); if (fixedHeightElement) { // 默认为hidden 显示异常 (fixedHeightElement as HTMLTableCellElement).style.overflow = 'visible'; } // 兜底方案:使用元素的父节点或文档体 return (node.parentNode as HTMLElement) || document.body; } ================================================ FILE: packages/utils/src/helpers/index.ts ================================================ export * from './enum-options'; export * from './find-menu-by-path'; export * from './generate-menus'; export * from './generate-routes-backend'; export * from './generate-routes-frontend'; export * from './get-popup-container'; export * from './merge-route-modules'; export * from './mitt'; export * from './request'; export * from './reset-routes'; export * from './safe'; export * from './tree'; export * from './unmount-global-loading'; export * from './uuid'; ================================================ FILE: packages/utils/src/helpers/merge-route-modules.ts ================================================ import type { RouteRecordRaw } from 'vue-router'; // 定义模块类型 interface RouteModuleType { default: RouteRecordRaw[]; } /** * 合并动态路由模块的默认导出 * @param routeModules 动态导入的路由模块对象 * @returns 合并后的路由配置数组 */ function mergeRouteModules( routeModules: Record, ): RouteRecordRaw[] { const mergedRoutes: RouteRecordRaw[] = []; for (const routeModule of Object.values(routeModules)) { const moduleRoutes = (routeModule as RouteModuleType)?.default ?? []; mergedRoutes.push(...moduleRoutes); } return mergedRoutes; } export { mergeRouteModules }; export type { RouteModuleType }; ================================================ FILE: packages/utils/src/helpers/mitt.ts ================================================ /** * copy to https://github.com/developit/mitt * Expand clear method */ export type EventType = string | symbol; // An event handler can take an optional event argument // and should not return a value export type Handler = (event: T) => void; export type WildcardHandler> = ( type: keyof T, event: T[keyof T], ) => void; // An array of all currently registered event handlers for a type export type EventHandlerList = Array>; export type WildCardEventHandlerList> = Array< WildcardHandler >; // A map of event types and their corresponding event handlers. export type EventHandlerMap> = Map< '*' | keyof Events, EventHandlerList | WildCardEventHandlerList >; export interface Emitter> { all: EventHandlerMap; clear(): void; emit( type: undefined extends Events[Key] ? Key : never, ): void; emit(type: Key, event: Events[Key]): void; off(type: '*', handler: WildcardHandler): void; off( type: Key, handler?: Handler, ): void; on(type: '*', handler: WildcardHandler): void; on(type: Key, handler: Handler): void; } /** * Mitt: Tiny (~200b) functional event emitter / pubsub. * @name mitt * @returns {Mitt} any */ export function mitt>( all?: EventHandlerMap, ): Emitter { type GenericEventHandler = | Handler | WildcardHandler; all = all || new Map(); return { /** * A Map of event names to registered handler functions. */ all, /** * Clear all */ clear() { this.all.clear(); }, /** * Invoke all handlers for the given type. * If present, `'*'` handlers are invoked after type-matched handlers. * * Note: Manually firing '*' handlers is not supported. * * @param {string|symbol} type The event type to invoke * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler * @memberOf mitt */ emit(type: Key, evt?: Events[Key]) { let handlers = all?.get(type); if (handlers) { [...(handlers as EventHandlerList)].forEach( (handler) => { handler(evt as Events[Key]); }, ); } handlers = all?.get('*'); if (handlers) { [...(handlers as WildCardEventHandlerList)].forEach( (handler) => { handler(type, evt as Events[Key]); }, ); } }, /** * Remove an event handler for the given type. * If `handler` is omitted, all handlers of the given type are removed. * @param {string|symbol} type Type of event to unregister `handler` from (`'*'` to remove a wildcard handler) * @param {Function} [handler] Handler function to remove * @memberOf mitt */ off(type: Key, handler?: GenericEventHandler) { const handlers: Array | undefined = all?.get(type); if (handlers) { if (handler) { handlers.splice(handlers.indexOf(handler) >>> 0, 1); } else { all?.set(type, []); } } }, /** * Register an event handler for the given type. * @param {string|symbol} type Type of event to listen for, or `'*'` for all events * @param {Function} handler Function to call in response to given event * @memberOf mitt */ on(type: Key, handler: GenericEventHandler) { const handlers: Array | undefined = all?.get(type); if (handlers) { handlers.push(handler); } else { all?.set(type, [handler] as EventHandlerList); } }, }; } ================================================ FILE: packages/utils/src/helpers/request.ts ================================================ /** * 一些发送请求 需要用到的工具 */ /** * Add the object as a parameter to the URL * @param baseUrl url * @param obj * @returns {string} * eg: * let obj = {a: '3', b: '4'} * setObjToUrlParams('www.baidu.com', obj) * ==>www.baidu.com?a=3&b=4 */ export function setObjToUrlParams(baseUrl: string, obj: any): string { let parameters = ''; for (const key in obj) { parameters += `${key}=${encodeURIComponent(obj[key])}&`; } parameters = parameters.replace(/&$/, ''); return /\?$/.test(baseUrl) ? baseUrl + parameters : baseUrl.replace(/\/?$/, '?') + parameters; } ================================================ FILE: packages/utils/src/helpers/reset-routes.ts ================================================ import type { Router, RouteRecordName, RouteRecordRaw } from 'vue-router'; import { traverseTreeValues } from '@vben-core/shared/utils'; /** * @zh_CN 重置所有路由,如有指定白名单除外 */ export function resetStaticRoutes(router: Router, routes: RouteRecordRaw[]) { // 获取静态路由所有节点包含子节点的 name,并排除不存在 name 字段的路由 const staticRouteNames = traverseTreeValues< RouteRecordRaw, RouteRecordName | undefined >(routes, (route) => { // 这些路由需要指定 name,防止在路由重置时,不能删除没有指定 name 的路由 if (!route.name) { console.warn( `The route with the path ${route.path} needs to have the field name specified.`, ); } return route.name; }); const { getRoutes, hasRoute, removeRoute } = router; const allRoutes = getRoutes(); allRoutes.forEach(({ name }) => { // 存在于路由表且非白名单才需要删除 if (name && !staticRouteNames.includes(name) && hasRoute(name)) { removeRoute(name); } }); } ================================================ FILE: packages/utils/src/helpers/safe.ts ================================================ /** * 跟后台逻辑一致 * Number.isSafeInteger形参只能为Number类型 其他的直接返回false * @param str 数字 * @returns 安全数内返回number类型 否则返回原字符串 */ export function safeParseNumber(str: string): number | string { const num = Number(str); return Number.isSafeInteger(num) ? num : str; } ================================================ FILE: packages/utils/src/helpers/tree.ts ================================================ /* eslint-disable @typescript-eslint/no-non-null-assertion */ interface TreeHelperConfig { children: string; id: string; pid: string; } type Fn = (node: any, parentNode?: any) => any; // 默认配置 const DEFAULT_CONFIG: TreeHelperConfig = { id: 'id', pid: 'parentId', children: 'children', }; // 获取配置。 Object.assign 从一个或多个源对象复制到目标对象 const getConfig = (config: Partial) => Object.assign({}, DEFAULT_CONFIG, config); // tree from list // 列表中的树 export function listToTree( list: any[], config: Partial = {}, ): T[] { const conf = getConfig(config) as TreeHelperConfig; const nodeMap = new Map(); const result: T[] = []; const { id, pid, children } = conf; for (const node of list) { node[children] = node[children] || []; nodeMap.set(node[id], node); } for (const node of list) { const parent = nodeMap.get(node[pid]); (parent ? parent[children] : result).push(node); } return result; } export function treeToList( tree: any, config: Partial = {}, ): T { config = getConfig(config); const { children } = config; const result: any = [...tree]; for (let i = 0; i < result.length; i++) { if (!result[i][children!]) continue; result.splice(i + 1, 0, ...result[i][children!]); } return result; } export function findNode( tree: any, func: Fn, config: Partial = {}, ): null | T { config = getConfig(config); const { children } = config; const list = [...tree]; for (const node of list) { if (func(node)) return node; node[children!] && list.push(...node[children!]); } return null; } export function findNodeAll( tree: any, func: Fn, config: Partial = {}, ): T[] { config = getConfig(config); const { children } = config; const list = [...tree]; const result: T[] = []; for (const node of list) { func(node) && result.push(node); node[children!] && list.push(...node[children!]); } return result; } export function findPath( tree: any, func: Fn, config: Partial = {}, ): null | T | T[] { config = getConfig(config); const path: T[] = []; const list = [...tree]; const visitedSet = new Set(); const { children } = config; while (list.length > 0) { const node = list[0]; if (visitedSet.has(node)) { path.pop(); list.shift(); } else { visitedSet.add(node); node[children!] && list.unshift(...node[children!]); path.push(node); if (func(node)) { return path; } } } return null; } export function findPathAll( tree: any, func: Fn, config: Partial = {}, ) { config = getConfig(config); const path: any[] = []; const list = [...tree]; const result: any[] = []; const { children } = config; const visitedSet = new Set(); while (list.length > 0) { const node = list[0]; if (visitedSet.has(node)) { path.pop(); list.shift(); } else { visitedSet.add(node); node[children!] && list.unshift(...node[children!]); path.push(node); func(node) && result.push([...path]); } } return result; } export function filter( tree: T[], func: (n: T) => boolean, // Partial 将 T 中的所有属性设为可选 config: Partial = {}, ): T[] { // 获取配置 config = getConfig(config); const children = config.children as string; function listFilter(list: T[]) { return list .map((node: any) => ({ ...node })) .filter((node) => { // 递归调用 对含有children项 进行再次调用自身函数 listFilter node[children] = node[children] && listFilter(node[children]); // 执行传入的回调 func 进行过滤 return func(node) || (node[children] && node[children].length > 0); }); } return listFilter(tree); } export function forEach( tree: T[], func: (n: T) => any, config: Partial = {}, ): void { config = getConfig(config); const list: any[] = [...tree]; const { children } = config; for (let i = 0; i < list.length; i++) { // func 返回true就终止遍历,避免大量节点场景下无意义循环,引起浏览器卡顿 if (func(list[i])) { return; } children && list[i][children] && list.splice(i + 1, 0, ...list[i][children]); } } /** * @description: Extract tree specified structure * @description: 提取树指定结构 */ export function treeMap( treeData: T[], opt: { children?: string; conversion: Fn }, ): T[] { return treeData.map((item) => treeMapEach(item, opt)); } /** * @description: Extract tree specified structure * @description: 提取树指定结构 */ export function treeMapEach( data: any, { conversion, children = 'children' }: { children?: string; conversion: Fn }, ) { const haveChildren = Array.isArray(data[children]) && data[children].length > 0; const conversionData = conversion(data) || {}; return haveChildren ? { ...conversionData, [children]: data[children].map((i: number) => treeMapEach(i, { children, conversion, }), ), } : { ...conversionData, }; } /** * 递归遍历树结构 * @param treeDatas 树 * @param callBack 回调 * @param parentNode 父节点 */ export function eachTree(treeDatas: any[], callBack: Fn, parentNode = {}) { treeDatas.forEach((element) => { const newNode = callBack(element, parentNode) || element; if (element.children) { eachTree(element.children, callBack, newNode); } }); } // 如果节点的children为空, 则删除children属性 export function removeEmptyChildren(data: any[], childrenField = 'children') { data.forEach((item) => { if (!item[childrenField]) { return; } if (item[childrenField].length > 0) { removeEmptyChildren(item[childrenField]); } else { Reflect.deleteProperty(item, childrenField); } }); } // eslint-disable-next-line jsdoc/require-returns-check /** * * 添加全名 如 祖先节点-父节点-子节点 * @param treeData 已经是tree数据 * @param labelName 标签的字段名称 * @param splitStr 分隔符 * @returns void 无返回值 会修改原始数据 */ export function addFullName( treeData: any[], labelName = 'label', splitStr = '-', ) { function addFullNameProperty(node: any, parentNames: any[] = []) { const fullNameParts = [...parentNames, node[labelName]]; node.fullName = fullNameParts.join(splitStr); if (node.children && node.children.length > 0) { node.children.forEach((childNode: any) => { addFullNameProperty(childNode, fullNameParts); }); } } treeData.forEach((item: any) => { addFullNameProperty(item); }); } /** * https://blog.csdn.net/Web_J/article/details/129281329 * 给出节点nodeId 找到所有父节点ID * @param treeList 树形结构list * @param nodeId 要寻找的节点ID * @param config config * @returns 父节点ID数组 */ export function findParentsIds( treeList: any[], nodeId: number, config: Partial = {}, ) { const conf = getConfig(config) as TreeHelperConfig; const { id, children } = conf; // 用于存储所有父节点ID的数组 const parentIds: number[] = []; function traverse(node: any, nodeId: number) { if (node[id] === nodeId) { return true; } if (node[children]) { // 如果当前节点有子节点,则继续遍历子节点 for (const childNode of node[children]) { if (traverse(childNode, nodeId)) { // 如果在子节点中找到了子节点的父节点,则将当前节点的ID添加到父节点ID数组中,并返回true表示已经找到了子节点 parentIds.push(node[id]); return true; } } } return false; } for (const node of treeList) { if (traverse(node, nodeId)) { // 如果在当前节点的子树中找到了子节点的父节点,则直接退出循环 break; } } return parentIds.sort(); } /** * 给出节点数组 找到所有父节点ID * @param treeList 树形结构list * @param nodeIds 要寻找的节点ID list * @param config config * @returns 父节点ID数组 */ export function findGroupParentIds( treeList: any[], nodeIds: number[], config: Partial = {}, ) { // 用于存储所有父节点ID的Set 主要为了去重 const parentIds = new Set(); nodeIds.forEach((nodeId) => { findParentsIds(treeList, nodeId, config).forEach((parentId) => { parentIds.add(parentId); }); }); return [...parentIds].sort(); } /** * 找到所有ID 返回数组 * @param treeList list * @param config * @returns ID数组 */ export function findAllIds( treeList: any[], config: Partial = DEFAULT_CONFIG, ) { const conf = getConfig(config) as TreeHelperConfig; const { id, children } = conf; const ids: number[] = []; treeList.forEach((item) => { if (item[children]) { const tempIds = findAllIds(item[children], config); ids.push(...tempIds); } ids.push(item[id]); }); return [...ids].sort(); } /** * @description 这里抄的filterByLevel函数 * @description 主要用于获取指定层级的节点数组 */ export function findIdsByLevel( level = 1, list?: any[], config: Partial = DEFAULT_CONFIG, currentLevel = 1, ) { if (!level) { return []; } const res: (number | string)[] = []; const data = list || []; for (const item of data) { const { id: keyField, children: childrenField } = config; const key = keyField ? item[keyField] : ''; const children = childrenField ? item[childrenField] : []; res.push(key); if (children && children.length > 0 && currentLevel < level) { currentLevel += 1; res.push(...findIdsByLevel(level, children, config, currentLevel)); } } return res as number[] | string[]; } ================================================ FILE: packages/utils/src/helpers/unmount-global-loading.ts ================================================ /** * 移除并销毁loading * 放在这里是而不是放在 index.html 的app标签内,是因为这样比较不会生硬,渲染过快可能会有闪烁 * 通过先添加css动画隐藏,在动画结束后在移除loading节点来改善体验 * 不好的地方是会增加一些代码量 * 自定义loading可以见:https://doc.vben.pro/guide/in-depth/loading.html */ export function unmountGlobalLoading() { // 查找全局 loading 元素 const loadingElement = document.querySelector('#__app-loading__'); if (loadingElement) { // 添加隐藏类,触发过渡动画 loadingElement.classList.add('hidden'); // 查找所有需要移除的注入 loading 元素 const injectLoadingElements = document.querySelectorAll( '[data-app-loading^="inject"]', ); // 当过渡动画结束时,移除 loading 元素和所有注入的 loading 元素 loadingElement.addEventListener( 'transitionend', () => { loadingElement.remove(); // 移除 loading 元素 injectLoadingElements.forEach((el) => el.remove()); // 移除所有注入的 loading 元素 }, { once: true }, ); // 确保事件只触发一次 } } ================================================ FILE: packages/utils/src/helpers/uuid.ts ================================================ const hexList: string[] = []; for (let i = 0; i <= 15; i++) { hexList[i] = i.toString(16); } export function buildUUID(): string { let uuid = ''; for (let i = 1; i <= 36; i++) { switch (i) { case 9: case 14: case 19: case 24: { uuid += '-'; break; } case 15: { uuid += 4; break; } case 20: { uuid += hexList[(Math.random() * 4) | 8]; break; } default: { uuid += hexList[Math.trunc(Math.random() * 16)]; } } } return uuid.replaceAll('-', ''); } let unique = 0; export function buildShortUUID(prefix = ''): string { const time = Date.now(); const random = Math.floor(Math.random() * 1_000_000_000); unique++; return `${prefix}_${random}${unique}${String(time)}`; } ================================================ FILE: packages/utils/src/index.ts ================================================ export * from './encryption'; export * from './helpers'; export * from '@vben-core/shared/cache'; export * from '@vben-core/shared/color'; export * from '@vben-core/shared/utils'; export { fileTypeFromBlob } from 'file-type'; ================================================ FILE: packages/utils/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/library.json", "compilerOptions": { "types": ["@vben-core/typings/vue-router"] }, "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: pnpm-workspace.yaml ================================================ packages: - internal/* - internal/lint-configs/* - packages/* - packages/@core/base/* - packages/@core/ui-kit/* - packages/@core/forward/* - packages/@core/* - packages/effects/* - packages/business/* - apps/* - scripts/* - docs - playground catalog: '@ast-grep/napi': ^0.37.0 '@changesets/changelog-github': ^0.5.1 '@changesets/cli': ^2.29.5 '@changesets/git': ^3.0.4 '@clack/prompts': ^0.10.1 '@commitlint/cli': ^19.8.1 '@commitlint/config-conventional': ^19.8.1 '@ctrl/tinycolor': ^4.1.0 '@eslint/js': ^9.30.1 '@faker-js/faker': ^9.9.0 '@iconify/json': ^2.2.354 '@iconify/tailwind': ^1.2.0 '@iconify/vue': ^5.0.0 '@intlify/core-base': ^11.1.7 '@intlify/unplugin-vue-i18n': ^6.0.8 '@jspm/generator': ^2.6.2 '@manypkg/get-packages': ^3.0.0 '@nolebase/vitepress-plugin-git-changelog': ^2.18.0 '@playwright/test': ^1.53.2 '@pnpm/workspace.read-manifest': ^1000.2.0 '@stylistic/stylelint-plugin': ^3.1.3 '@tailwindcss/nesting': 0.0.0-insiders.565cd3e '@tailwindcss/typography': ^0.5.16 '@tanstack/vue-query': ^5.81.5 '@tanstack/vue-store': ^0.7.1 '@types/archiver': ^6.0.3 '@types/eslint': ^9.6.1 '@types/html-minifier-terser': ^7.0.2 '@types/json-bigint': ^1.0.4 '@types/jsonwebtoken': ^9.0.10 '@types/lodash.clonedeep': ^4.5.9 '@types/lodash.get': ^4.4.9 '@types/lodash.isequal': ^4.5.8 '@types/lodash.set': ^4.3.9 '@types/node': ^22.16.0 '@types/nprogress': ^0.2.3 '@types/postcss-import': ^14.0.3 '@types/qrcode': ^1.5.5 '@types/qs': ^6.14.0 '@types/sortablejs': ^1.15.8 '@typescript-eslint/eslint-plugin': ^8.35.1 '@typescript-eslint/parser': ^8.35.1 '@vee-validate/zod': ^4.15.1 '@vite-pwa/vitepress': ^1.0.0 '@vitejs/plugin-vue': ^6.0.1 '@vitejs/plugin-vue-jsx': ^5.0.1 '@vue/reactivity': ^3.5.17 '@vue/shared': ^3.5.17 '@vue/test-utils': ^2.4.6 '@vueuse/core': ^13.4.0 '@vueuse/integrations': ^13.4.0 '@vueuse/motion': ^3.0.3 ant-design-vue: ^4.2.6 archiver: ^7.0.1 autoprefixer: ^10.4.21 axios: ^1.10.0 axios-mock-adapter: ^2.1.0 cac: ^6.7.14 chalk: ^5.4.1 cheerio: ^1.1.0 circular-dependency-scanner: ^2.3.0 class-variance-authority: ^0.7.1 clsx: ^2.1.1 commitlint-plugin-function-rules: ^4.0.2 consola: ^3.4.2 cross-env: ^7.0.3 cspell: ^8.19.4 cssnano: ^7.0.7 cz-git: ^1.11.2 czg: ^1.11.1 dayjs: ^1.11.13 defu: ^6.1.4 depcheck: ^1.4.7 dotenv: ^16.6.1 echarts: ^5.6.0 element-plus: ^2.10.2 eslint: ^9.30.1 eslint-config-turbo: ^2.5.4 eslint-plugin-command: ^3.3.1 eslint-plugin-eslint-comments: ^3.2.0 eslint-plugin-import-x: ^4.16.1 eslint-plugin-jsdoc: ^50.8.0 eslint-plugin-jsonc: ^2.20.1 eslint-plugin-n: ^17.20.0 eslint-plugin-no-only-tests: ^3.3.0 eslint-plugin-perfectionist: ^4.15.0 eslint-plugin-prettier: ^5.5.1 eslint-plugin-regexp: ^2.9.0 eslint-plugin-unicorn: ^59.0.1 eslint-plugin-unused-imports: ^4.1.4 eslint-plugin-vitest: ^0.5.4 eslint-plugin-vue: ^10.2.0 execa: ^9.6.0 find-up: ^7.0.0 get-port: ^7.1.0 globals: ^16.3.0 h3: ^1.15.3 happy-dom: ^17.6.3 html-minifier-terser: ^7.2.0 is-ci: ^4.1.0 json-bigint: ^1.0.0 jsonc-eslint-parser: ^2.4.0 jsonwebtoken: ^9.0.2 lefthook: 1.11.12 lodash.clonedeep: ^4.5.0 lodash.get: ^4.4.2 lodash.isequal: ^4.5.0 lodash.set: ^4.3.2 lucide-vue-next: ^0.507.0 medium-zoom: ^1.1.0 naive-ui: ^2.42.0 nitropack: ^2.11.13 nprogress: ^0.2.0 ora: ^8.2.0 pinia: ^3.0.3 pinia-plugin-persistedstate: ^4.4.1 pkg-types: ^2.2.0 playwright: ^1.53.2 postcss: ^8.5.6 postcss-antd-fixes: ^0.2.0 postcss-html: ^1.8.0 postcss-import: ^16.1.1 postcss-preset-env: ^10.2.4 postcss-scss: ^4.0.9 prettier: ^3.6.2 prettier-plugin-tailwindcss: ^0.6.13 publint: ^0.3.12 qrcode: ^1.5.4 qs: ^6.14.0 radix-vue: ^1.9.17 resolve.exports: ^2.0.3 rimraf: ^6.0.1 rollup: ^4.44.1 rollup-plugin-visualizer: ^5.14.0 sass: ^1.89.2 secure-ls: ^2.0.0 sortablejs: ^1.15.6 stylelint: ^16.21.0 stylelint-config-recess-order: ^6.1.0 stylelint-config-recommended: ^16.0.0 stylelint-config-recommended-scss: ^14.1.0 stylelint-config-recommended-vue: ^1.6.1 stylelint-config-standard: ^38.0.0 stylelint-order: ^7.0.0 stylelint-prettier: ^5.0.3 stylelint-scss: ^6.12.1 tailwind-merge: ^2.6.0 tailwindcss: ^3.4.17 tailwindcss-animate: ^1.0.7 theme-colors: ^0.1.0 tippy.js: ^6.3.7 turbo: ^2.5.4 typescript: ^5.8.3 unbuild: ^3.6.1 unplugin-element-plus: ^0.10.0 vee-validate: ^4.15.1 vite: ^7.1.2 vite-plugin-compression: ^0.5.1 vite-plugin-dts: ^4.5.4 vite-plugin-html: ^3.2.2 vite-plugin-lazy-import: ^1.0.7 vite-plugin-pwa: ^1.0.1 vite-plugin-vue-devtools: ^7.7.7 vitepress: ^1.6.3 vitepress-plugin-group-icons: ^1.6.1 vitest: ^3.2.4 vue: ^3.5.17 vue-eslint-parser: ^10.2.0 vue-i18n: ^11.1.7 vue-json-viewer: ^3.0.4 vue-router: ^4.5.1 vue-tippy: ^6.7.1 vue-tsc: 2.2.10 vxe-pc-ui: 4.10.36 vxe-table: ^4.16.11 watermark-js-plus: ^1.6.2 zod: ^3.25.67 zod-defaults: ^0.1.3 ================================================ FILE: scripts/clean.mjs ================================================ import { promises as fs } from 'node:fs'; import { join, normalize } from 'node:path'; const rootDir = process.cwd(); // 控制并发数量,避免创建过多的并发任务 const CONCURRENCY_LIMIT = 10; // 需要跳过的目录,避免进入这些目录进行清理 const SKIP_DIRS = new Set(['.DS_Store', '.git', '.idea', '.vscode']); /** * 处理单个文件/目录项 * @param {string} currentDir - 当前目录路径 * @param {string} item - 文件/目录名 * @param {string[]} targets - 要删除的目标列表 * @param {number} _depth - 当前递归深度 * @returns {Promise} - 是否需要进一步递归处理 */ async function processItem(currentDir, item, targets, _depth) { // 跳过特殊目录 if (SKIP_DIRS.has(item)) { return false; } try { const itemPath = normalize(join(currentDir, item)); if (targets.includes(item)) { // 匹配到目标目录或文件时直接删除 await fs.rm(itemPath, { force: true, recursive: true }); console.log(`✅ Deleted: ${itemPath}`); return false; // 已删除,无需递归 } // 使用 readdir 的 withFileTypes 选项,避免额外的 lstat 调用 return true; // 可能需要递归,由调用方决定 } catch (error) { // 更详细的错误信息 if (error.code === 'ENOENT') { // 文件不存在,可能已被删除,这是正常情况 return false; } else if (error.code === 'EPERM' || error.code === 'EACCES') { console.error(`❌ Permission denied: ${item} in ${currentDir}`); } else { console.error( `❌ Error handling item ${item} in ${currentDir}: ${error.message}`, ); } return false; } } /** * 递归查找并删除目标目录(并发优化版本) * @param {string} currentDir - 当前遍历的目录路径 * @param {string[]} targets - 要删除的目标列表 * @param {number} depth - 当前递归深度,避免过深递归 */ async function cleanTargetsRecursively(currentDir, targets, depth = 0) { // 限制递归深度,避免无限递归 if (depth > 10) { console.warn(`Max recursion depth reached at: ${currentDir}`); return; } let dirents; try { // 使用 withFileTypes 选项,一次性获取文件类型信息,避免后续 lstat 调用 dirents = await fs.readdir(currentDir, { withFileTypes: true }); } catch (error) { // 如果无法读取目录,可能已被删除或权限不足 console.warn(`Cannot read directory ${currentDir}: ${error.message}`); return; } // 分批处理,控制并发数量 for (let i = 0; i < dirents.length; i += CONCURRENCY_LIMIT) { const batch = dirents.slice(i, i + CONCURRENCY_LIMIT); const tasks = batch.map(async (dirent) => { const item = dirent.name; const shouldRecurse = await processItem(currentDir, item, targets, depth); // 如果是目录且没有被删除,则递归处理 if (shouldRecurse && dirent.isDirectory()) { const itemPath = normalize(join(currentDir, item)); return cleanTargetsRecursively(itemPath, targets, depth + 1); } return null; }); // 并发执行当前批次的任务 const results = await Promise.allSettled(tasks); // 检查是否有失败的任务(可选:用于调试) const failedTasks = results.filter( (result) => result.status === 'rejected', ); if (failedTasks.length > 0) { console.warn( `${failedTasks.length} tasks failed in batch starting at index ${i} in directory: ${currentDir}`, ); } } } (async function startCleanup() { // 要删除的目录及文件名称 const targets = ['node_modules', 'dist', '.turbo', 'dist.zip']; const deleteLockFile = process.argv.includes('--del-lock'); const cleanupTargets = [...targets]; if (deleteLockFile) { cleanupTargets.push('pnpm-lock.yaml'); } console.log( `🚀 Starting cleanup of targets: ${cleanupTargets.join(', ')} from root: ${rootDir}`, ); const startTime = Date.now(); try { // 先统计要删除的目标数量 console.log('📊 Scanning for cleanup targets...'); await cleanTargetsRecursively(rootDir, cleanupTargets); const endTime = Date.now(); const duration = (endTime - startTime) / 1000; console.log( `✨ Cleanup process completed successfully in ${duration.toFixed(2)}s`, ); } catch (error) { console.error(`💥 Unexpected error during cleanup: ${error.message}`); process.exit(1); } })(); ================================================ FILE: scripts/deploy/Dockerfile ================================================ FROM node:22-slim AS builder # --max-old-space-size ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" ENV NODE_OPTIONS=--max-old-space-size=8192 ENV TZ=Asia/Shanghai RUN npm i -g corepack WORKDIR /app # copy package.json and pnpm-lock.yaml to workspace COPY . /app # 安装依赖 RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN pnpm run build --filter=\!./docs RUN echo "Builder Success 🎉" FROM nginx:stable-alpine AS production # 配置 nginx RUN echo "types { application/javascript js mjs; }" > /etc/nginx/conf.d/mjs.conf \ && rm -rf /etc/nginx/conf.d/default.conf # 复制构建产物 COPY --from=builder /app/playground/dist /usr/share/nginx/html # 复制 nginx 配置 COPY --from=builder /app/scripts/deploy/nginx.conf /etc/nginx/nginx.conf EXPOSE 8080 # 启动 nginx CMD ["nginx", "-g", "daemon off;"] ================================================ FILE: scripts/deploy/build-local-docker-image.sh ================================================ #!/bin/bash SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) LOG_FILE=${SCRIPT_DIR}/build-local-docker-image.log ERROR="" IMAGE_NAME="vben-admin-local" function stop_and_remove_container() { # Stop and remove the existing container docker stop ${IMAGE_NAME} >/dev/null 2>&1 docker rm ${IMAGE_NAME} >/dev/null 2>&1 } function remove_image() { # Remove the existing image docker rmi vben-admin-pro >/dev/null 2>&1 } function install_dependencies() { # Install all dependencies cd ${SCRIPT_DIR} pnpm install || ERROR="install_dependencies failed" } function build_image() { # build docker docker build ../../ -f Dockerfile -t ${IMAGE_NAME} || ERROR="build_image failed" } function log_message() { if [[ ${ERROR} != "" ]]; then >&2 echo "build failed, Please check build-local-docker-image.log for more details" >&2 echo "ERROR: ${ERROR}" exit 1 else echo "docker image with tag '${IMAGE_NAME}' built sussessfully. Use below sample command to run the container" echo "" echo "docker run -d -p 8010:8080 --name ${IMAGE_NAME} ${IMAGE_NAME}" fi } echo "Info: Stopping and removing existing container and image" | tee ${LOG_FILE} stop_and_remove_container remove_image echo "Info: Installing dependencies" | tee -a ${LOG_FILE} install_dependencies 1>> ${LOG_FILE} 2>> ${LOG_FILE} if [[ ${ERROR} == "" ]]; then echo "Info: Building docker image" | tee -a ${LOG_FILE} build_image 1>> ${LOG_FILE} 2>> ${LOG_FILE} fi log_message | tee -a ${LOG_FILE} ================================================ FILE: scripts/deploy/nginx.conf ================================================ #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; types { application/javascript js mjs; text/css css; text/html html; } sendfile on; # tcp_nopush on; #keepalive_timeout 0; # keepalive_timeout 65; # gzip on; # gzip_buffers 32 16k; # gzip_comp_level 6; # gzip_min_length 1k; # gzip_static on; # gzip_types text/plain # text/css # application/javascript # application/json # application/x-javascript # text/xml # application/xml # application/xml+rss # text/javascript; #设置压缩的文件类型 # gzip_vary on; server { listen 8080; server_name localhost; location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; index index.html; # Enable CORS add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain charset=UTF-8'; add_header 'Content-Length' 0; return 204; } } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } } } ================================================ FILE: scripts/turbo-run/README.md ================================================ # @vben/turbo-run `turbo-run` 是一个命令行工具,允许你在多个包中并行运行命令。它提供了一个交互式的界面,让你可以选择要运行命令的包。 ## 特性 - 🚀 交互式选择要运行的包 - 📦 支持 monorepo 项目结构 - 🔍 自动检测可用的命令 - 🎯 精确过滤目标包 ## 安装 ```bash pnpm add -D @vben/turbo-run ``` ## 使用方法 基本语法: ```bash turbo-run [script] ``` 例如,如果你想运行 `dev` 命令: ```bash turbo-run dev ``` 工具会自动检测哪些包有 `dev` 命令,并提供一个交互式界面让你选择要运行的包。 ## 示例 假设你的项目中有以下包: - `@vben/app` - `@vben/admin` - `@vben/website` 当你运行: ```bash turbo-run dev ``` 工具会: 1. 检测哪些包有 `dev` 命令 2. 显示一个交互式选择界面 3. 让你选择要运行命令的包 4. 使用 `pnpm --filter` 在选定的包中运行命令 ## 注意事项 - 确保你的项目使用 pnpm 作为包管理器 - 确保目标包在 `package.json` 中定义了相应的脚本命令 - 该工具需要在 monorepo 项目的根目录下运行 ================================================ FILE: scripts/turbo-run/bin/turbo-run.mjs ================================================ #!/usr/bin/env node import('../dist/index.mjs'); ================================================ FILE: scripts/turbo-run/build.config.ts ================================================ import { defineBuildConfig } from 'unbuild'; export default defineBuildConfig({ clean: true, declaration: true, entries: ['src/index'], }); ================================================ FILE: scripts/turbo-run/package.json ================================================ { "name": "@vben/turbo-run", "version": "5.5.9", "private": true, "license": "MIT", "type": "module", "scripts": { "stub": "pnpm unbuild --stub" }, "files": [ "dist" ], "bin": { "turbo-run": "./bin/turbo-run.mjs" }, "main": "./dist/index.mjs", "module": "./dist/index.mjs", "exports": { ".": { "default": "./dist/index.mjs" }, "./package.json": "./package.json" }, "dependencies": { "@clack/prompts": "catalog:", "@vben/node-utils": "workspace:*", "cac": "catalog:" } } ================================================ FILE: scripts/turbo-run/src/index.ts ================================================ import { colors, consola } from '@vben/node-utils'; import { cac } from 'cac'; import { run } from './run'; try { const turboRun = cac('turbo-run'); turboRun .command('[script]') .usage(`Run turbo interactively.`) .action(async (command: string) => { run({ command }); }); // Invalid command turboRun.on('command:*', () => { consola.error(colors.red('Invalid command!')); process.exit(1); }); turboRun.usage('turbo-run'); turboRun.help(); turboRun.parse(); } catch (error) { consola.error(error); process.exit(1); } ================================================ FILE: scripts/turbo-run/src/run.ts ================================================ import { execaCommand, getPackages } from '@vben/node-utils'; import { cancel, isCancel, select } from '@clack/prompts'; interface RunOptions { command?: string; } export async function run(options: RunOptions) { const { command } = options; if (!command) { console.error('Please enter the command to run'); process.exit(1); } const { packages } = await getPackages(); // const appPkgs = await findApps(process.cwd(), packages); // const websitePkg = packages.find( // (item) => item.packageJson.name === '@vben/website', // ); // 只显示有对应命令的包 const selectPkgs = packages.filter((pkg) => { return (pkg?.packageJson as Record)?.scripts?.[command]; }); let selectPkg: string | symbol; if (selectPkgs.length > 1) { selectPkg = await select({ message: `Select the app you need to run [${command}]:`, options: selectPkgs.map((item) => ({ label: item?.packageJson.name, value: item?.packageJson.name, })), }); if (isCancel(selectPkg) || !selectPkg) { cancel('👋 Has cancelled'); process.exit(0); } } else { selectPkg = selectPkgs[0]?.packageJson?.name ?? ''; } if (!selectPkg) { console.error('No app found'); process.exit(1); } execaCommand(`pnpm --filter=${selectPkg} run ${command}`, { stdio: 'inherit', }); } /** * 过滤app包 * @param root * @param packages */ // async function findApps(root: string, packages: Package[]) { // // apps内的 // const appPackages = packages.filter((pkg) => { // const viteConfigExists = fs.existsSync(join(pkg.dir, 'vite.config.mts')); // return pkg.dir.startsWith(join(root, 'apps')) && viteConfigExists; // }); // return appPackages; // } ================================================ FILE: scripts/turbo-run/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/node.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: scripts/vsh/README.md ================================================ # @vben/vsh 一个 Shell 脚本工具集合,用于 Vue Vben Admin 项目的开发和管理。 ## 功能特性 - 🚀 基于 Node.js 的现代化 Shell 工具 - 📦 支持模块化开发和按需加载 - 🔍 提供依赖检查和分析功能 - 🔄 支持循环依赖扫描 - 📝 提供包发布检查功能 ## 安装 ```bash # 使用 pnpm 安装 pnpm add -D @vben/vsh # 或者使用 npm npm install -D @vben/vsh # 或者使用 yarn yarn add -D @vben/vsh ``` ## 使用方法 ### 全局安装 ```bash # 全局安装 pnpm add -g @vben/vsh # 使用 vsh 命令 vsh [command] ``` ### 本地使用 ```bash # 在 package.json 中添加脚本 { "scripts": { "vsh": "vsh" } } # 运行命令 pnpm vsh [command] ``` ## 命令列表 - `vsh check-deps`: 检查项目依赖 - `vsh scan-circular`: 扫描循环依赖 - `vsh publish-check`: 检查包发布配置 ================================================ FILE: scripts/vsh/bin/vsh.mjs ================================================ #!/usr/bin/env node import('../dist/index.mjs'); ================================================ FILE: scripts/vsh/build.config.ts ================================================ import { defineBuildConfig } from 'unbuild'; export default defineBuildConfig({ clean: true, declaration: true, entries: ['src/index'], }); ================================================ FILE: scripts/vsh/package.json ================================================ { "name": "@vben/vsh", "version": "5.5.9", "private": true, "license": "MIT", "type": "module", "scripts": { "stub": "pnpm unbuild --stub" }, "files": [ "dist" ], "bin": { "vsh": "./bin/vsh.mjs" }, "main": "./dist/index.mjs", "module": "./dist/index.mjs", "exports": { ".": { "default": "./dist/index.mjs" }, "./package.json": "./package.json" }, "dependencies": { "@vben/node-utils": "workspace:*", "cac": "catalog:", "circular-dependency-scanner": "catalog:", "depcheck": "catalog:", "publint": "catalog:" } } ================================================ FILE: scripts/vsh/src/check-circular/index.ts ================================================ import type { CAC } from 'cac'; import { extname } from 'node:path'; import { getStagedFiles } from '@vben/node-utils'; import { circularDepsDetect } from 'circular-dependency-scanner'; // 默认配置 const DEFAULT_CONFIG = { allowedExtensions: ['.cjs', '.js', '.jsx', '.mjs', '.ts', '.tsx', '.vue'], ignoreDirs: [ 'dist', '.turbo', 'output', '.cache', 'scripts', 'internal', 'packages/effects/request/src/', 'packages/@core/ui-kit/menu-ui/src/', 'packages/@core/ui-kit/popup-ui/src/', ], threshold: 0, // 循环依赖的阈值 } as const; // 类型定义 type CircularDependencyResult = string[]; interface CheckCircularConfig { allowedExtensions?: string[]; ignoreDirs?: string[]; threshold?: number; } interface CommandOptions { config?: CheckCircularConfig; staged: boolean; verbose: boolean; } // 缓存机制 const cache = new Map(); /** * 格式化循环依赖的输出 * @param circles - 循环依赖结果 */ function formatCircles(circles: CircularDependencyResult[]): void { if (circles.length === 0) { console.log('✅ No circular dependencies found'); return; } console.log('⚠️ Circular dependencies found:'); circles.forEach((circle, index) => { console.log(`\nCircular dependency #${index + 1}:`); circle.forEach((file) => console.log(` → ${file}`)); }); } /** * 检查项目中的循环依赖 * @param options - 检查选项 * @param options.staged - 是否只检查暂存区文件 * @param options.verbose - 是否显示详细信息 * @param options.config - 自定义配置 * @returns Promise */ async function checkCircular({ config = {}, staged, verbose, }: CommandOptions): Promise { try { // 合并配置 const finalConfig = { ...DEFAULT_CONFIG, ...config, }; // 生成忽略模式 const ignorePattern = `**/{${finalConfig.ignoreDirs.join(',')}}/**`; // 检查缓存 const cacheKey = `${staged}-${process.cwd()}-${ignorePattern}`; if (cache.has(cacheKey)) { const cachedResults = cache.get(cacheKey); if (cachedResults) { verbose && formatCircles(cachedResults); } return; } // 检测循环依赖 const results = await circularDepsDetect({ absolute: staged, cwd: process.cwd(), ignore: [ignorePattern], }); if (staged) { let files = await getStagedFiles(); const allowedExtensions = new Set(finalConfig.allowedExtensions); // 过滤文件列表 files = files.filter((file) => allowedExtensions.has(extname(file))); const circularFiles: CircularDependencyResult[] = []; for (const file of files) { for (const result of results) { const resultFiles = result.flat(); if (resultFiles.includes(file)) { circularFiles.push(result); } } } // 更新缓存 cache.set(cacheKey, circularFiles); verbose && formatCircles(circularFiles); } else { // 更新缓存 cache.set(cacheKey, results); verbose && formatCircles(results); } // 如果发现循环依赖,只输出警告信息 if (results.length > 0) { console.log( '\n⚠️ Warning: Circular dependencies found, please check and fix', ); } } catch (error) { console.error( '❌ Error checking circular dependencies:', error instanceof Error ? error.message : error, ); } } /** * 定义检查循环依赖的命令 * @param cac - CAC实例 */ function defineCheckCircularCommand(cac: CAC): void { cac .command('check-circular') .option('--staged', 'Only check staged files') .option('--verbose', 'Show detailed information') .option('--threshold ', 'Threshold for circular dependencies', { default: 0, }) .option('--ignore-dirs ', 'Directories to ignore, comma separated') .usage('Analyze project circular dependencies') .action(async ({ ignoreDirs, staged, threshold, verbose }) => { const config: CheckCircularConfig = { threshold: Number(threshold), ...(ignoreDirs && { ignoreDirs: ignoreDirs.split(',') }), }; await checkCircular({ config, staged, verbose: verbose ?? true, }); }); } export { type CheckCircularConfig, defineCheckCircularCommand }; ================================================ FILE: scripts/vsh/src/check-dep/index.ts ================================================ import type { CAC } from 'cac'; import { getPackages } from '@vben/node-utils'; import depcheck from 'depcheck'; // 默认配置 const DEFAULT_CONFIG = { // 需要忽略的依赖匹配 ignoreMatches: [ 'vite', 'vitest', 'unbuild', '@vben/tsconfig', '@vben/vite-config', '@vben/tailwind-config', '@types/*', '@vben-core/design', ], // 需要忽略的包 ignorePackages: [ '@vben/backend-mock', '@vben/commitlint-config', '@vben/eslint-config', '@vben/node-utils', '@vben/prettier-config', '@vben/stylelint-config', '@vben/tailwind-config', '@vben/tsconfig', '@vben/vite-config', '@vben/vsh', ], // 需要忽略的文件模式 ignorePatterns: ['dist', 'node_modules', 'public'], }; interface DepcheckResult { dependencies: string[]; devDependencies: string[]; missing: Record; } interface DepcheckConfig { ignoreMatches?: string[]; ignorePackages?: string[]; ignorePatterns?: string[]; } interface PackageInfo { dir: string; packageJson: { name: string; }; } /** * 清理依赖检查结果 * @param unused - 依赖检查结果 */ function cleanDepcheckResult(unused: DepcheckResult): void { // 删除file:前缀的依赖提示,该依赖是本地依赖 Reflect.deleteProperty(unused.missing, 'file:'); // 清理路径依赖 Object.keys(unused.missing).forEach((key) => { unused.missing[key] = (unused.missing[key] || []).filter( (item: string) => !item.startsWith('/'), ); if (unused.missing[key].length === 0) { Reflect.deleteProperty(unused.missing, key); } }); } /** * 格式化依赖检查结果 * @param pkgName - 包名 * @param unused - 依赖检查结果 */ function formatDepcheckResult(pkgName: string, unused: DepcheckResult): void { const hasIssues = Object.keys(unused.missing).length > 0 || unused.dependencies.length > 0 || unused.devDependencies.length > 0; if (!hasIssues) { return; } console.log('\n📦 Package:', pkgName); if (Object.keys(unused.missing).length > 0) { console.log('❌ Missing dependencies:'); Object.entries(unused.missing).forEach(([dep, files]) => { console.log(` - ${dep}:`); files.forEach((file) => console.log(` → ${file}`)); }); } if (unused.dependencies.length > 0) { console.log('⚠️ Unused dependencies:'); unused.dependencies.forEach((dep) => console.log(` - ${dep}`)); } if (unused.devDependencies.length > 0) { console.log('⚠️ Unused devDependencies:'); unused.devDependencies.forEach((dep) => console.log(` - ${dep}`)); } } /** * 运行依赖检查 * @param config - 配置选项 */ async function runDepcheck(config: DepcheckConfig = {}): Promise { try { const finalConfig = { ...DEFAULT_CONFIG, ...config, }; const { packages } = await getPackages(); let hasIssues = false; await Promise.all( packages.map(async (pkg: PackageInfo) => { // 跳过需要忽略的包 if (finalConfig.ignorePackages.includes(pkg.packageJson.name)) { return; } const unused = await depcheck(pkg.dir, { ignoreMatches: finalConfig.ignoreMatches, ignorePatterns: finalConfig.ignorePatterns, }); cleanDepcheckResult(unused); const pkgHasIssues = Object.keys(unused.missing).length > 0 || unused.dependencies.length > 0 || unused.devDependencies.length > 0; if (pkgHasIssues) { hasIssues = true; formatDepcheckResult(pkg.packageJson.name, unused); } }), ); if (!hasIssues) { console.log('\n✅ Dependency check completed, no issues found'); } } catch (error) { console.error( '❌ Dependency check failed:', error instanceof Error ? error.message : error, ); } } /** * 定义依赖检查命令 * @param cac - CAC实例 */ function defineDepcheckCommand(cac: CAC): void { cac .command('check-dep') .option( '--ignore-packages ', 'Packages to ignore, comma separated', ) .option( '--ignore-matches ', 'Dependency patterns to ignore, comma separated', ) .option( '--ignore-patterns ', 'File patterns to ignore, comma separated', ) .usage('Analyze project dependencies') .action(async ({ ignoreMatches, ignorePackages, ignorePatterns }) => { const config: DepcheckConfig = { ...(ignorePackages && { ignorePackages: ignorePackages.split(',') }), ...(ignoreMatches && { ignoreMatches: ignoreMatches.split(',') }), ...(ignorePatterns && { ignorePatterns: ignorePatterns.split(',') }), }; await runDepcheck(config); }); } export { defineDepcheckCommand, type DepcheckConfig }; ================================================ FILE: scripts/vsh/src/code-workspace/index.ts ================================================ import type { CAC } from 'cac'; import { join, relative } from 'node:path'; import { colors, consola, findMonorepoRoot, getPackages, gitAdd, outputJSON, prettierFormat, toPosixPath, } from '@vben/node-utils'; const CODE_WORKSPACE_FILE = join('vben-admin.code-workspace'); interface CodeWorkspaceCommandOptions { autoCommit?: boolean; spaces?: number; } async function createCodeWorkspace({ autoCommit = false, spaces = 2, }: CodeWorkspaceCommandOptions) { const { packages, rootDir } = await getPackages(); let folders = packages.map((pkg) => { const { dir, packageJson } = pkg; return { name: packageJson.name, path: toPosixPath(relative(rootDir, dir)), }; }); folders = folders.filter(Boolean); const monorepoRoot = findMonorepoRoot(); const outputPath = join(monorepoRoot, CODE_WORKSPACE_FILE); await outputJSON(outputPath, { folders }, spaces); await prettierFormat(outputPath); if (autoCommit) { await gitAdd(CODE_WORKSPACE_FILE, monorepoRoot); } } async function runCodeWorkspace({ autoCommit, spaces, }: CodeWorkspaceCommandOptions) { await createCodeWorkspace({ autoCommit, spaces, }); if (autoCommit) { return; } consola.log(''); consola.success(colors.green(`${CODE_WORKSPACE_FILE} is updated!`)); consola.log(''); } function defineCodeWorkspaceCommand(cac: CAC) { cac .command('code-workspace') .usage('Update the `.code-workspace` file') .option('--spaces [number]', '.code-workspace JSON file spaces.', { default: 2, }) .option('--auto-commit', 'auto commit .code-workspace JSON file.', { default: false, }) .action(runCodeWorkspace); } export { defineCodeWorkspaceCommand }; ================================================ FILE: scripts/vsh/src/index.ts ================================================ import { colors, consola } from '@vben/node-utils'; import { cac } from 'cac'; import { version } from '../package.json'; import { defineCheckCircularCommand } from './check-circular'; import { defineDepcheckCommand } from './check-dep'; import { defineCodeWorkspaceCommand } from './code-workspace'; import { defineLintCommand } from './lint'; import { definePubLintCommand } from './publint'; // 命令描述 const COMMAND_DESCRIPTIONS = { 'check-circular': 'Check for circular dependencies', 'check-dep': 'Check for unused dependencies', 'code-workspace': 'Manage VS Code workspace settings', lint: 'Run linting on the project', publint: 'Check package.json files for publishing standards', } as const; /** * Initialize and run the CLI */ async function main(): Promise { try { const vsh = cac('vsh'); // Register commands defineLintCommand(vsh); definePubLintCommand(vsh); defineCodeWorkspaceCommand(vsh); defineCheckCircularCommand(vsh); defineDepcheckCommand(vsh); // Handle invalid commands vsh.on('command:*', ([cmd]) => { consola.error( colors.red(`Invalid command: ${cmd}`), '\n', colors.yellow('Available commands:'), '\n', Object.entries(COMMAND_DESCRIPTIONS) .map(([cmd, desc]) => ` ${colors.cyan(cmd)} - ${desc}`) .join('\n'), ); process.exit(1); }); // Set up CLI vsh.usage('vsh [options]'); vsh.help(); vsh.version(version); // Parse arguments vsh.parse(); } catch (error) { consola.error( colors.red('An unexpected error occurred:'), '\n', error instanceof Error ? error.message : error, ); process.exit(1); } } // Run the CLI main().catch((error) => { consola.error( colors.red('Failed to start CLI:'), '\n', error instanceof Error ? error.message : error, ); process.exit(1); }); ================================================ FILE: scripts/vsh/src/lint/index.ts ================================================ import type { CAC } from 'cac'; import { execaCommand } from '@vben/node-utils'; interface LintCommandOptions { /** * Format lint problem. */ format?: boolean; } async function runLint({ format }: LintCommandOptions) { // process.env.FORCE_COLOR = '3'; if (format) { await execaCommand(`stylelint "**/*.{vue,css,less,scss}" --cache --fix`, { stdio: 'inherit', }); await execaCommand(`eslint . --cache --fix`, { stdio: 'inherit', }); await execaCommand(`prettier . --write --cache --log-level warn`, { stdio: 'inherit', }); return; } await Promise.all([ execaCommand(`eslint . --cache`, { stdio: 'inherit', }), execaCommand(`prettier . --ignore-unknown --check --cache`, { stdio: 'inherit', }), execaCommand(`stylelint "**/*.{vue,css,less,scss}" --cache`, { stdio: 'inherit', }), ]); } function defineLintCommand(cac: CAC) { cac .command('lint') .usage('Batch execute project lint check.') .option('--format', 'Format lint problem.') .action(runLint); } export { defineLintCommand }; ================================================ FILE: scripts/vsh/src/publint/index.ts ================================================ import type { CAC } from 'cac'; import type { Result } from 'publint'; import { basename, dirname, join } from 'node:path'; import { colors, consola, ensureFile, findMonorepoRoot, generatorContentHash, getPackages, outputJSON, readJSON, UNICODE, } from '@vben/node-utils'; import { publint } from 'publint'; import { formatMessage } from 'publint/utils'; const CACHE_FILE = join( 'node_modules', '.cache', 'publint', '.pkglintcache.json', ); interface PubLintCommandOptions { /** * Only errors are checked, no program exit is performed */ check?: boolean; } /** * Get files that require lint * @param files */ async function getLintFiles(files: string[] = []) { const lintFiles: string[] = []; if (files?.length > 0) { return files.filter((file) => basename(file) === 'package.json'); } const { packages } = await getPackages(); for (const { dir } of packages) { lintFiles.push(join(dir, 'package.json')); } return lintFiles; } function getCacheFile() { const root = findMonorepoRoot(); return join(root, CACHE_FILE); } async function readCache(cacheFile: string) { try { await ensureFile(cacheFile); return await readJSON(cacheFile); } catch { return {}; } } async function runPublint(files: string[], { check }: PubLintCommandOptions) { const lintFiles = await getLintFiles(files); const cacheFile = getCacheFile(); const cacheData = await readCache(cacheFile); const cache: Record = cacheData; const results = await Promise.all( lintFiles.map(async (file) => { try { const pkgJson = await readJSON(file); if (pkgJson.private) { return null; } Reflect.deleteProperty(pkgJson, 'dependencies'); Reflect.deleteProperty(pkgJson, 'devDependencies'); Reflect.deleteProperty(pkgJson, 'peerDependencies'); const content = JSON.stringify(pkgJson); const hash = generatorContentHash(content); const publintResult: Result = cache?.[file]?.hash === hash ? (cache?.[file]?.result ?? []) : await publint({ level: 'suggestion', pkgDir: dirname(file), strict: true, }); cache[file] = { hash, result: publintResult, }; return { pkgJson, pkgPath: file, publintResult }; } catch { return null; } }), ); await outputJSON(cacheFile, cache); printResult(results, check); } function printResult( results: Array<{ pkgJson: Record; pkgPath: string; publintResult: Result; } | null>, check?: boolean, ) { let errorCount = 0; let warningCount = 0; let suggestionsCount = 0; for (const result of results) { if (!result) { continue; } const { pkgJson, pkgPath, publintResult } = result; const messages = publintResult?.messages ?? []; if (messages?.length < 1) { continue; } consola.log(''); consola.log(pkgPath); for (const message of messages) { switch (message.type) { case 'error': { errorCount++; break; } case 'suggestion': { suggestionsCount++; break; } case 'warning': { warningCount++; break; } // No default } const ruleUrl = `https://publint.dev/rules#${message.code.toLocaleLowerCase()}`; consola.log( ` ${formatMessage(message, pkgJson)}${colors.dim(` ${ruleUrl}`)}`, ); } } const totalCount = warningCount + errorCount + suggestionsCount; if (totalCount > 0) { consola.error( colors.red( `${UNICODE.FAILURE} ${totalCount} problem (${errorCount} errors, ${warningCount} warnings, ${suggestionsCount} suggestions)`, ), ); !check && process.exit(1); } else { consola.log(colors.green(`${UNICODE.SUCCESS} No problem`)); } } function definePubLintCommand(cac: CAC) { cac .command('publint [...files]') .usage('Check if the monorepo package conforms to the publint standard.') .option('--check', 'Only errors are checked, no program exit is performed.') .action(runPublint); } export { definePubLintCommand }; ================================================ FILE: scripts/vsh/tsconfig.json ================================================ { "$schema": "https://json.schemastore.org/tsconfig", "extends": "@vben/tsconfig/node.json", "include": ["src"], "exclude": ["node_modules"] } ================================================ FILE: stylelint.config.mjs ================================================ export default { extends: ['@vben/stylelint-config'], root: true, }; ================================================ FILE: turbo.json ================================================ { "$schema": "https://turbo.build/schema.json", "globalDependencies": [ "pnpm-lock.yaml", "**/.env.*local", "**/tsconfig*.json", "internal/node-utils/*.json", "internal/node-utils/src/**/*.ts", "internal/tailwind-config/src/**/*.ts", "internal/vite-config/*.json", "internal/vite-config/src/**/*.ts", "scripts/*/src/**/*.ts", "scripts/*/src/**/*.json" ], "globalEnv": ["NODE_ENV"], "tasks": { "build": { "dependsOn": ["^build"], "outputs": [ "dist/**", "dist.zip", ".vitepress/dist.zip", ".vitepress/dist/**" ] }, "@vben/web-antd#build:prod": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "@vben/web-antd#build:test": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "preview": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "build:analyze": { "dependsOn": ["^build"], "outputs": ["dist/**"] }, "@vben/backend-mock#build": { "dependsOn": ["^build"], "outputs": [".nitro/**", ".output/**"] }, "test:e2e": {}, "dev": { "dependsOn": [], "outputs": [], "cache": false, "persistent": true }, "typecheck": { "outputs": [] } } }