diff --git a/app/frontend/src/javascript/components/base/text-editor/fab-text-editor.tsx b/app/frontend/src/javascript/components/base/text-editor/fab-text-editor.tsx index 47d173953..667df3b30 100644 --- a/app/frontend/src/javascript/components/base/text-editor/fab-text-editor.tsx +++ b/app/frontend/src/javascript/components/base/text-editor/fab-text-editor.tsx @@ -20,15 +20,16 @@ interface FabTextEditorProps { paragraphTools?: boolean, content?: string, limit?: number, + video?: boolean, onChange?: (content: string) => void, placeholder?: string, - error?: string, + error?: string } /** * This component is a WYSIWYG text editor */ -export const FabTextEditor: React.FC = ({ label, paragraphTools, content, limit = 400, onChange, placeholder, error }) => { +export const FabTextEditor: React.FC = ({ label, paragraphTools, content, limit = 400, video, onChange, placeholder, error }) => { const { t } = useTranslation('shared'); const placeholderText = placeholder || t('app.shared.text_editor.placeholder'); // TODO: Add ctrl+click on link to visit @@ -69,7 +70,7 @@ export const FabTextEditor: React.FC = ({ label, paragraphTo <> {label && }
- +
{editor?.storage.characterCount.characters()} / {limit} @@ -85,12 +86,12 @@ export const FabTextEditor: React.FC = ({ label, paragraphTo ); }; -const FabTextEditorWrapper: React.FC = ({ label, paragraphTools, content, limit, placeholder, error }) => { +const FabTextEditorWrapper: React.FC = ({ label, paragraphTools, content, limit, video, placeholder, error }) => { return ( - + ); }; -Application.Components.component('fabTextEditor', react2angular(FabTextEditorWrapper, ['label', 'paragraphTools', 'content', 'limit', 'placeholder', 'error'])); +Application.Components.component('fabTextEditor', react2angular(FabTextEditorWrapper, ['label', 'paragraphTools', 'content', 'limit', 'video', 'placeholder', 'error'])); diff --git a/app/frontend/src/javascript/components/base/text-editor/menu-bar.tsx b/app/frontend/src/javascript/components/base/text-editor/menu-bar.tsx index 0448aa3c6..6809feca2 100644 --- a/app/frontend/src/javascript/components/base/text-editor/menu-bar.tsx +++ b/app/frontend/src/javascript/components/base/text-editor/menu-bar.tsx @@ -6,34 +6,39 @@ import { TextAa, TextBolder, TextItalic, TextUnderline, LinkSimpleHorizontal, Li interface MenuBarProps { paragraphTools?: boolean, - extra?: boolean, + video?: boolean, editor?: Editor, } /** * This component is the menu bar for the WYSIWYG text editor */ -export const MenuBar: React.FC = ({ editor, paragraphTools, extra }) => { +export const MenuBar: React.FC = ({ editor, paragraphTools, video }) => { const { t } = useTranslation('shared'); - const [linkMenu, setLinkMenu] = useState(false); + const [submenu, setSubmenu] = useState(''); const resetUrl = { href: '', target: '_blank' }; const [url, setUrl] = useState(resetUrl); - const ref = useOnclickOutside(() => { - setLinkMenu(false); - }); + const [videoProvider, setVideoProvider] = useState('youtube'); + const [videoId, setVideoId] = useState(''); - // Reset state values when the link menu is closed + // Reset state values when the submenu is closed useEffect(() => { - if (!linkMenu) { + if (!submenu) { setUrl(resetUrl); + setVideoProvider('youtube'); } - }, [linkMenu]); + }, [submenu]); + + // Close the submenu frame on click outside + const ref = useOnclickOutside(() => { + setSubmenu(''); + }); // Toggle link menu's visibility const toggleLinkMenu = () => { - if (!linkMenu) { - setLinkMenu(true); + if (submenu !== 'link') { + setSubmenu('link'); const previousUrl = { href: editor.getAttributes('link').href, target: editor.getAttributes('link').target || '' @@ -43,8 +48,7 @@ export const MenuBar: React.FC = ({ editor, paragraphTools, extra setUrl(previousUrl); } } else { - setLinkMenu(false); - setUrl(resetUrl); + setSubmenu(''); } }; @@ -56,7 +60,7 @@ export const MenuBar: React.FC = ({ editor, paragraphTools, extra }; // Update url - const handleChange = (evt) => { + const linkUrlChange = (evt) => { setUrl({ ...url, href: evt.target.value }); }; // Support keyboard "Enter" key event to validate @@ -74,19 +78,52 @@ export const MenuBar: React.FC = ({ editor, paragraphTools, extra } editor.chain().focus().extendMarkRange('link').setLink({ href: url.href, target: url.target }).run(); if (closeLinkMenu) { - setLinkMenu(false); + setSubmenu(''); } }, [editor, url]); // Remove the link tag from the selected text const unsetLink = () => { editor.chain().focus().extendMarkRange('link').unsetLink().run(); - setLinkMenu(false); + setSubmenu(''); }; - // Add iFrame + // Toggle video menu's visibility + const toggleVideoMenu = () => { + if (submenu !== 'video') { + setSubmenu('video'); + } else { + setSubmenu(''); + } + }; + + // Store selected video provider in state + const handleSelect = (evt) => { + setVideoProvider(evt.target.value); + }; + // Store video id in state + const VideoUrlChange = (evt) => { + const id = evt.target.value.match(/([^/]+$)/g); + setVideoId(id); + }; + // Insert iframe containing the video player const addIframe = () => { - editor.chain().focus().setIframe({ src: 'https://www.youtube.com/embed/XIMLoLxmTDw' }).run(); + let videoUrl = ''; + switch (videoProvider) { + case 'youtube': + videoUrl = `https://www.youtube.com/embed/${videoId}`; + break; + case 'vimeo': + videoUrl = `https://player.vimeo.com/video/${videoId}`; + break; + case 'dailymotion': + videoUrl = `https://www.dailymotion.com/embed/video/${videoId}`; + break; + default: + break; + } + editor.chain().focus().setIframe({ src: videoUrl }).run(); + setSubmenu(''); }; if (!editor) { @@ -150,34 +187,53 @@ export const MenuBar: React.FC = ({ editor, paragraphTools, extra > - { extra && + { video && (<> ) }
-
-
- - -
-
- - -
+
+ { submenu === 'link' && + (<> +
+ + +
+
+ + +
+ ) + } + { submenu === 'video' && + (<> + +
+ + +
+ ) + }
); diff --git a/app/frontend/src/stylesheets/modules/base/fab-text-editor.scss b/app/frontend/src/stylesheets/modules/base/fab-text-editor.scss index 5ec63f5a2..8da40fedd 100644 --- a/app/frontend/src/stylesheets/modules/base/fab-text-editor.scss +++ b/app/frontend/src/stylesheets/modules/base/fab-text-editor.scss @@ -49,7 +49,10 @@ // tiptap class for the editor .ProseMirror { + max-height: 40vh; padding: 1.6rem 1.6rem 1.2rem; + overflow: auto; + resize: vertical; &:focus { outline: none; } @include editor; } @@ -61,7 +64,7 @@ color: var(--gray-hard-lightest); } - &-linkMenu { + &-subMenu { position: absolute; top: 4.5rem; right: 0; @@ -83,25 +86,35 @@ } & > div { display: flex; - align-items: center + align-items: center; + &:not(:last-of-type) { margin-bottom: 0.8rem; } } - .url { + + input[type="text"], + select { + width: 100%; + height: 4rem; + padding: 0.4rem 0.8rem; + background-color: var(--gray-soft-light); + border: 1px solid var(--secondary); + border-radius: var(--border-radius); + font-size: var(--text-base); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + } + input[type="text"] { + margin-right: 1.2rem; + &::placeholder { color: var(--gray-soft-darkest);} + } + select { margin-bottom: 0.8rem; - input { - width: 100%; - height: 4rem; - margin-right: 1.2rem; - padding: 0.4rem 0.8rem; - background-color: var(--gray-soft-light); - border: 1px solid var(--secondary); - border-radius: var(--border-radius); - font-size: var(--text-base); - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - &::placeholder { color: var(--gray-soft-darkest);} - } } + button { + @include button(3.2rem); + margin-left: auto; + } + .tab { display: flex; align-items: center; @@ -137,11 +150,6 @@ border-color: var(--information); } } - - button { - @include button(3.2rem); - margin-left: auto; - } } &-video { @@ -149,8 +157,14 @@ height: 0; width: 100%; max-width: 600px; - padding-bottom: calc(100 / 16 * 9); + padding-bottom: calc(100% / 16 * 9); overflow: hidden; + + iframe { + position: absolute; + max-width: 100%; + inset: 0; + } } &-error { diff --git a/package.json b/package.json index c73016964..a0b9032b4 100644 --- a/package.json +++ b/package.json @@ -58,12 +58,13 @@ "@rails/webpacker": "5.4.3", "@stripe/react-stripe-js": "^1.4.0", "@stripe/stripe-js": "^1.13.2", + "@tiptap/core": "^2.0.0-beta.174", "@tiptap/extension-character-count": "^2.0.0-beta.24", "@tiptap/extension-link": "^2.0.0-beta.36", "@tiptap/extension-placeholder": "^2.0.0-beta.47", "@tiptap/extension-underline": "^2.0.0-beta.22", - "@tiptap/react": "^2.0.0-beta.107", - "@tiptap/starter-kit": "^2.0.0-beta.180", + "@tiptap/react": "^2.0.0-beta.108", + "@tiptap/starter-kit": "^2.0.0-beta.183", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", "@uirouter/angularjs": "0.4", diff --git a/yarn.lock b/yarn.lock index a03ff01f1..ff10d97c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1379,10 +1379,10 @@ resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.15.1.tgz#a3809ecc5aa8a03bd261a2f970d11cfdcbf11c4f" integrity sha512-yJiDGutlwu25iajCy51VRJeoH3UMs+s5qVIDGfmPUuFpZ+F6AJ9g9EFrsBNvHxAGBahQFMLlBdzlCVydhGp6tg== -"@tiptap/core@^2.0.0-beta.171": - version "2.0.0-beta.171" - resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.171.tgz#e681964c443383b81d2638c51fc3bbfda034a4fb" - integrity sha512-4CdJfcchmBOFooWPBMJ7AxJISeTstMFriQv0RyReMt0Dpef/c9UoU+NkKLwwv5VRUX0M8dL5SzEhkB8wIODqlA== +"@tiptap/core@^2.0.0-beta.174": + version "2.0.0-beta.174" + resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.174.tgz#cfdf16b7d7401e4b255dc69147d784f5f537b942" + integrity sha512-APQDto40PdvagG1HTwkKlieQS4Vp6GXNe7qgV1Qo2QCgJCLyxc/fXCTghtrOx0CQb+9JT7fjSLZxbSyUFXjx7Q== dependencies: "@types/prosemirror-commands" "^1.0.4" "@types/prosemirror-keymap" "^1.0.4" @@ -1404,10 +1404,10 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-blockquote/-/extension-blockquote-2.0.0-beta.26.tgz#e5ae4b7bd9376db37407a23e22080c7b11287f3b" integrity sha512-A6yjcYovONJfOjQFk6vDYXswaCdCtCwjL7w9VTB0R2DLTuJvvRt9DWN0IDcMrj5G+aMgDq4GUUTitv+2Y8krDg== -"@tiptap/extension-bold@^2.0.0-beta.25": - version "2.0.0-beta.25" - resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.25.tgz#ec19e7c862d25bae49609c5d6a873f372c506dee" - integrity sha512-ZNdgFYDxKo8lAp0Pqzu45I0JH3ah8/X5TCYg9zNg3QwLUFT16g2LlWDMUDGT5pH9aXxgtFaEdoVacu0EyhlPnQ== +"@tiptap/extension-bold@^2.0.0-beta.26": + version "2.0.0-beta.26" + resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.26.tgz#aa1c7850df28cec8e0614fde437183bd4ae3e66b" + integrity sha512-pnO0I5sEQM3pmowjMGQ74adLzvc6HqGyLyqMizaGMicPu9uTYlSdId+qckYEEgPwPMaEShtv2Vg+ZHs7KVqfcg== "@tiptap/extension-bubble-menu@^2.0.0-beta.55": version "2.0.0-beta.55" @@ -1495,10 +1495,10 @@ dependencies: prosemirror-state "^1.3.4" -"@tiptap/extension-italic@^2.0.0-beta.25": - version "2.0.0-beta.25" - resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.25.tgz#c2ec95cc5baf855134883c5e261da4ab0d3b9479" - integrity sha512-7PvhioTX9baVp5+AmmZU0qna+dFPZCRlSEN/GciH57N77d2uhJ/ZW5iQWTbvy5HBNddQB4Jts1UDIaC7WASrGA== +"@tiptap/extension-italic@^2.0.0-beta.26": + version "2.0.0-beta.26" + resolved "https://registry.yarnpkg.com/@tiptap/extension-italic/-/extension-italic-2.0.0-beta.26.tgz#b00c9e32b81b1bd94eaed24bb2a22e44d5dc54a3" + integrity sha512-vejGe2ra4K5ipFOn1U9viqF9X9nPTX8WSJpSOux+9UbKjHpANy7bz69tp66OIi/Wh5L/MMDc+luH/04qfVnpZw== "@tiptap/extension-link@^2.0.0-beta.36": version "2.0.0-beta.36" @@ -1548,23 +1548,23 @@ resolved "https://registry.yarnpkg.com/@tiptap/extension-underline/-/extension-underline-2.0.0-beta.22.tgz#e6b83be0c0944183b47aa30d53f2ab5cd7defe23" integrity sha512-c+tOv4CRBG2pgtAACEsDwvbmM8C89M/CeelTcLLu8zrk+PRy7yj8DKLUtcb9Ybsa7f1Suk6iqyj3dkfxuuvDLw== -"@tiptap/react@^2.0.0-beta.107": - version "2.0.0-beta.107" - resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.0.0-beta.107.tgz#82e02bd83e9d3d37e31197f915968213124330c4" - integrity sha512-4G14F32TxEuYNYyOOQQxJ3ddFooPOv9Opcw5mjKxgKFZLzlBMTBl7os9ndrMfVdWYTI3RpZlfiAQxq3QlHamxA== +"@tiptap/react@^2.0.0-beta.108": + version "2.0.0-beta.108" + resolved "https://registry.yarnpkg.com/@tiptap/react/-/react-2.0.0-beta.108.tgz#65beeb4dfb99bc75e4a370cb24b2cbbd42ae13a7" + integrity sha512-uYfYx0em6rUoaTkwblYIzT6WLsNGA65XyP5PH56yLvHoOz+Q+skJMBkfrJ7n75tuWa3zMS+W9iYtybxaqPP0aw== dependencies: "@tiptap/extension-bubble-menu" "^2.0.0-beta.55" "@tiptap/extension-floating-menu" "^2.0.0-beta.50" prosemirror-view "^1.23.6" -"@tiptap/starter-kit@^2.0.0-beta.180": - version "2.0.0-beta.180" - resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.180.tgz#bea8101fdc25de88eb2fb76d29a3d16f4e94028e" - integrity sha512-dX+RxfAZa5MyY/XPr8iW+VTX/Qy3MIhCWApw15zHfLOdO80inl3to9JG5XS9oSMI2/SqsRe9XKz47Lj8srhw7A== +"@tiptap/starter-kit@^2.0.0-beta.183": + version "2.0.0-beta.183" + resolved "https://registry.yarnpkg.com/@tiptap/starter-kit/-/starter-kit-2.0.0-beta.183.tgz#35ff9f4b236bd321ffdd44c5061514959c1b4b9d" + integrity sha512-Lcms6lEfFfdL1oHoATcNKfu1C8+yhuZnI5Pq+U6o2zSslfnUSDf3jgmy6nSoZrrkqvFoXjQk4dxMDFg3giw2Kg== dependencies: - "@tiptap/core" "^2.0.0-beta.171" + "@tiptap/core" "^2.0.0-beta.174" "@tiptap/extension-blockquote" "^2.0.0-beta.26" - "@tiptap/extension-bold" "^2.0.0-beta.25" + "@tiptap/extension-bold" "^2.0.0-beta.26" "@tiptap/extension-bullet-list" "^2.0.0-beta.26" "@tiptap/extension-code" "^2.0.0-beta.26" "@tiptap/extension-code-block" "^2.0.0-beta.37" @@ -1575,7 +1575,7 @@ "@tiptap/extension-heading" "^2.0.0-beta.26" "@tiptap/extension-history" "^2.0.0-beta.21" "@tiptap/extension-horizontal-rule" "^2.0.0-beta.31" - "@tiptap/extension-italic" "^2.0.0-beta.25" + "@tiptap/extension-italic" "^2.0.0-beta.26" "@tiptap/extension-list-item" "^2.0.0-beta.20" "@tiptap/extension-ordered-list" "^2.0.0-beta.27" "@tiptap/extension-paragraph" "^2.0.0-beta.23" @@ -1680,9 +1680,9 @@ "@types/prosemirror-view" "*" "@types/prosemirror-model@*", "@types/prosemirror-model@^1.16.0": - version "1.16.0" - resolved "https://registry.yarnpkg.com/@types/prosemirror-model/-/prosemirror-model-1.16.0.tgz#8b22c7431a4c93f7f550fc89c4b0e2d44d42c8b6" - integrity sha512-nv93YLyTEcDDl17OB90EldxZjyJQJll2WSMLDvLzTewbpvE/vtMjHT3j4mik3uSzQ6YD486AcloCO3WODY/lDg== + version "1.16.1" + resolved "https://registry.yarnpkg.com/@types/prosemirror-model/-/prosemirror-model-1.16.1.tgz#0ce6c80cd81b398b8a11b1bf7cf695bff3160c9a" + integrity sha512-SrrCe2cHlYrQ9o55e2i/c3wt1yRajTTpRLvzfmB+2DWjWEbBLTByVWyjrdpKtQTxAaTeU2aeDGo1iuwl/jF27w== dependencies: "@types/orderedmap" "*" @@ -1705,9 +1705,9 @@ "@types/prosemirror-view" "*" "@types/prosemirror-transform@*", "@types/prosemirror-transform@^1.1.5": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@types/prosemirror-transform/-/prosemirror-transform-1.1.5.tgz#e6949398c64a5d3ca53e6081352751aa9e9ce76e" - integrity sha512-Wr2HXaEF4JPklWpC17RTxE6PxyU54Taqk5FMhK1ojgcN93J+GpkYW8s0mD3rl7KfTmlhVwZPCHE9o0cYf2Go5A== + version "1.1.6" + resolved "https://registry.yarnpkg.com/@types/prosemirror-transform/-/prosemirror-transform-1.1.6.tgz#4a06979f656331c46c2725039a57360cc35853af" + integrity sha512-7HwXOWc5vZQqIfEUUVAz13lPgBqAWJTv89qEpzAtBFB6hOszFmhsvQ02Jqe2LvKauAoJDa3Qpv/dbJAmgyiTuQ== dependencies: "@types/prosemirror-model" "*" @@ -7569,9 +7569,9 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: react-is "^16.8.1" prosemirror-commands@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.2.1.tgz#eae0cb714df695260659b78ff5d201d3a037e50d" - integrity sha512-S/IkpXfpuLFsRynC2HQ5iYROUPiZskKS1+ClcWycGJvj4HMb/mVfeEkQrixYxgTl96EAh+RZQNWPC06GZXk5tQ== + version "1.2.2" + resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.2.2.tgz#1bd167372ee20abf488aca9cece63c43fab182c9" + integrity sha512-TX+KpWudMon06frryfpO/u7hsQv2hu8L4VSVbCpi3/7wXHBgl+35mV85qfa3RpT8xD2f3MdeoTqH0vy5JdbXPg== dependencies: prosemirror-model "^1.0.0" prosemirror-state "^1.0.0" @@ -7587,9 +7587,9 @@ prosemirror-dropcursor@^1.4.0: prosemirror-view "^1.1.0" prosemirror-gapcursor@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.2.1.tgz#02365e1bcc1ad25d390b0fb7f0e94a7fc173ad75" - integrity sha512-PHa9lj27iM/g4C46gxVzsefuXVfy/LrGQH4QjMRht7VDBgw77iWYWn8ZHMWSFkwtr9jQEuxI5gccHHHwWG80nw== + version "1.2.2" + resolved "https://registry.yarnpkg.com/prosemirror-gapcursor/-/prosemirror-gapcursor-1.2.2.tgz#7394613ba4a1601fad1f36f1cff8961968c22ffa" + integrity sha512-7YzuRBbu9W7HGQde84kCHfIjaRLNcAdeijbgqrm/R9dsdTWkV+rrdcmic/sCc+bptiNpvjCEE+R6hrbT8zFQeQ== dependencies: prosemirror-keymap "^1.0.0" prosemirror-model "^1.0.0" @@ -7636,14 +7636,30 @@ prosemirror-state@^1.0.0, prosemirror-state@^1.2.2, prosemirror-state@^1.3.4: prosemirror-model "^1.0.0" prosemirror-transform "^1.0.0" -prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transform@^1.3.3: +prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0: version "1.3.3" resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.3.3.tgz#5f6712b0577a119cc418686fe7588b6dd9b7464d" integrity sha512-9NLVXy1Sfa2G6qPqhWMkEvwQQMTw7OyTqOZbJaGQWsCeH3hH5Cw+c5eNaLM1Uu75EyKLsEZhJ93XpHJBa6RX8A== dependencies: prosemirror-model "^1.0.0" -prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.23.6: +prosemirror-transform@^1.3.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/prosemirror-transform/-/prosemirror-transform-1.4.0.tgz#057344b7ef38d1a4ba370390eba8c35f9afe6b36" + integrity sha512-P+bv4JiLHcRy4krHByUglXR1yAMCuzHRAaSKInsoW7Rjy3aomPXM/MwRs+b7TGtC1e6ZM31KbapbvE4wV1X9RA== + dependencies: + prosemirror-model "^1.0.0" + +prosemirror-view@^1.0.0, prosemirror-view@^1.1.0: + version "1.23.10" + resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.23.10.tgz#a3fb6a7c780c8cd84488fdd451c23becab9dbefb" + integrity sha512-/p8Orb1VeJEbf7Z/BltU9GMWADZRqKlna6TlQGK1snJ6fTdLRC4f4yF2MgNK4OMQjmAwJISUtEp5+Vu5CSbR1w== + dependencies: + prosemirror-model "^1.16.0" + prosemirror-state "^1.0.0" + prosemirror-transform "^1.1.0" + +prosemirror-view@^1.23.6: version "1.23.6" resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.23.6.tgz#f514b3166942cb70aac4ac24d0a28c21c3897608" integrity sha512-B4DAzriNpI/AVoW0Lu6SVfX00jZZQxOVwdBQEjWlRbCdT9V0pvk4GQJ3JTFaib+b6BcPdRZ3MjWXz2xvV1rblA==