├── .firebaserc
├── .idea
├── .gitignore
├── vcs.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
└── podcast_app.iml
├── firebase.json
├── src
├── utils.js
├── styles.css
├── auth.js
├── index.html
├── app.js
└── question.js
├── package.json
├── .github
└── workflows
│ ├── firebase-hosting-merge.yml
│ └── firebase-hosting-pull-request.yml
├── webpack.config.js
├── .gitignore
└── public
├── index.html
└── bundle.08d1dbcd66acf7aa8ecc.js
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "podcast--app-fd631"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "public",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | export function isValid(value) {
2 | return value.length >= 10
3 | }
4 |
5 | export function createModal(title, content) {
6 | const modal = document.createElement('div')
7 | modal.classList.add('modal')
8 |
9 | const html = `
10 |
${title}
11 | ${content}
12 | `
13 |
14 | modal.innerHTML = html
15 |
16 | mui.overlay('on', modal)
17 | }
--------------------------------------------------------------------------------
/.idea/podcast_app.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "podcast_app",
3 | "version": "1.0.0",
4 | "description": "podcast app Sensei",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --mode development --open",
8 | "build": "webpack --mode production"
9 | },
10 | "keywords": [
11 | "sensei"
12 | ],
13 | "author": "Omka",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "clean-webpack-plugin": "^4.0.0",
17 | "css-loader": "^6.8.1",
18 | "html-webpack-plugin": "^5.5.3",
19 | "style-loader": "^3.3.3",
20 | "webpack": "^5.88.2",
21 | "webpack-cli": "^5.1.4",
22 | "webpack-dev-server": "^4.15.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.github/workflows/firebase-hosting-merge.yml:
--------------------------------------------------------------------------------
1 | # This file was auto-generated by the Firebase CLI
2 | # https://github.com/firebase/firebase-tools
3 |
4 | name: Deploy to Firebase Hosting on merge
5 | 'on':
6 | push:
7 | branches:
8 | - main
9 | jobs:
10 | build_and_deploy:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - run: npm ci && npm run build
15 | - uses: FirebaseExtended/action-hosting-deploy@v0
16 | with:
17 | repoToken: '${{ secrets.GITHUB_TOKEN }}'
18 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PODCAST__APP_FD631 }}'
19 | channelId: live
20 | projectId: podcast--app-fd631
21 |
--------------------------------------------------------------------------------
/.github/workflows/firebase-hosting-pull-request.yml:
--------------------------------------------------------------------------------
1 | # This file was auto-generated by the Firebase CLI
2 | # https://github.com/firebase/firebase-tools
3 |
4 | name: Deploy to Firebase Hosting on PR
5 | 'on': pull_request
6 | jobs:
7 | build_and_preview:
8 | if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v3
12 | - run: npm ci && npm run build
13 | - uses: FirebaseExtended/action-hosting-deploy@v0
14 | with:
15 | repoToken: '${{ secrets.GITHUB_TOKEN }}'
16 | firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PODCAST__APP_FD631 }}'
17 | projectId: podcast--app-fd631
18 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HTMLPlugin = require('html-webpack-plugin');
3 | const { CleanWebpackPlugin } = require('clean-webpack-plugin');
4 |
5 | module.exports = {
6 | entry: './src/app.js',
7 | output: {
8 | filename: "bundle.[chunkhash].js",
9 | path: path.resolve(__dirname, 'public')
10 | },
11 | devServer: {
12 | port: 3000
13 | },
14 | plugins: [
15 | new HTMLPlugin({
16 | template: "./src/index.html"
17 | }),
18 | new CleanWebpackPlugin()
19 | ],
20 | module: {
21 | rules: [
22 | {
23 | test: /\.css$/i,
24 | use: ['style-loader', 'css-loader']
25 | }
26 | ]
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Body CSS
3 | */
4 |
5 | html,
6 | body {
7 | height: 100%;
8 | }
9 |
10 | html,
11 | body,
12 | input,
13 | textarea,
14 | button {
15 | -webkit-font-smoothing: antialiased;
16 | -moz-osx-font-smoothing: grayscale;
17 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);
18 | }
19 |
20 |
21 | /**
22 | * Sidebar CSS
23 | */
24 |
25 | #sidebar {
26 | background-color: #2196f3;
27 | padding: 15px;
28 | }
29 |
30 | @media (min-width: 768px) {
31 | #sidebar {
32 | position: fixed;
33 | top: 0;
34 | bottom: 0;
35 | width: 180px;
36 | height: 100%;
37 | padding-top: 30px;
38 | }
39 | }
40 |
41 | .author {
42 | font-size: .8rem;
43 | }
44 |
45 |
46 | /**
47 | * Content CSS
48 | */
49 | @media (min-width: 768px) {
50 | #content {
51 | margin-left: 180px;
52 | }
53 | }
54 |
55 | .floating-btn {
56 | position: fixed;
57 | bottom: 50px;
58 | right: 50px;
59 | }
60 |
61 | .modal {
62 | max-width: 600px;
63 | max-height: 300px;
64 | margin: 100px auto;
65 | overflow-y: auto;
66 | background-color: #fff;
67 | }
68 |
69 | .modal > h1{
70 | text-align: center;
71 | }
72 | .modal .modal-content {
73 | padding: 1rem;
74 | }
75 |
76 | .error {
77 | color: red;
78 | }
--------------------------------------------------------------------------------
/src/auth.js:
--------------------------------------------------------------------------------
1 | export function getAuthForm() {
2 | return `
3 |
19 | `
20 | }
21 |
22 | export function authWithEmailAndPassword(email, password) {
23 | const apiKey = 'AIzaSyDOYvjw4CQtRxLhOJZLwSK8KWDvEMrALz4'
24 | return fetch(`https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${apiKey}`, { // Note the '=' after 'key'
25 | method: "POST",
26 | body: JSON.stringify({
27 | email,
28 | password,
29 | returnSecureToken: true
30 | }),
31 | headers: {
32 | 'Content-Type': 'application/json'
33 | }
34 | })
35 | .then(response => response.json())
36 | .then(data => data.idToken)
37 | }
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | firebase-debug.log*
8 | firebase-debug.*.log*
9 |
10 | # Firebase cache
11 | .firebase/
12 |
13 | # Firebase config
14 |
15 | # Uncomment this if you'd like others to create their own Firebase project.
16 | # For a team working on the same Firebase project(s), it is recommended to leave
17 | # it commented so all members can deploy to the same project(s) in .firebaserc.
18 | # .firebaserc
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (http://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Podcast
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
Задай Вопрос
22 |
23 |
24 |
32 |
33 |
Ваши вопросы
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Podcast
10 |
11 |
12 |
13 |
20 |
21 |
22 |
23 |
24 |
25 |
Задай Вопрос
26 |
27 |
28 |
42 |
43 |
Ваши вопросы
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import {Question} from './question'
2 | import './styles.css'
3 | import {createModal, isValid} from "./utils"
4 | import {authWithEmailAndPassword, getAuthForm} from "./auth";
5 |
6 | const modalBtn = document.getElementById('modal-btn')
7 | const form = document.getElementById('form')
8 | const input = form.querySelector('#question-input')
9 | window.addEventListener('load', Question.renderList)
10 |
11 | const submitBtn = form.querySelector('#submit')
12 | form.addEventListener('submit', submitFormHandler)
13 | modalBtn.addEventListener('click', openModal)
14 | input.addEventListener('input', () => {
15 | submitBtn.disabled = !isValid(input.value)
16 | })
17 |
18 | function submitFormHandler(event) {
19 | event.preventDefault()
20 |
21 | if (isValid(input.value)) {
22 | const question = {
23 | text: input.value.trim(),
24 | data: new Date().toJSON()
25 | }
26 | submitBtn.disabled = true
27 | //Async request to server to save question
28 | Question.create(question)
29 | .then(() => {
30 | input.value = ''
31 | input.className = ''
32 | submitBtn.disabled = false
33 | })
34 | .catch(error => {
35 | console.error("Error while saving the question:", error)
36 | submitBtn.disabled = false
37 | })
38 | }
39 | }
40 |
41 | function openModal() {
42 | createModal('Авторизация', getAuthForm())
43 | document.getElementById('auth-form')
44 | .addEventListener('submit', authFormHandler, {once: true})
45 | }
46 |
47 | function authFormHandler(event) {
48 | event.preventDefault()
49 |
50 | const btn = event.target.querySelector('button')
51 | const email = event.target.querySelector('#email').value
52 | const password = event.target.querySelector('#password').value
53 |
54 | btn.disabled = true
55 | authWithEmailAndPassword(email, password)
56 | .then(Question.fetch)
57 | .then(renderModalAfterAuth)
58 | .then(() => btn.disabled = false)
59 | }
60 |
61 | function renderModalAfterAuth(content) {
62 | if (typeof content === 'string') {
63 | createModal('Error' , content)
64 | }else {
65 | createModal('Список вопросов', Question.listToHtml(content))
66 | }
67 | }
--------------------------------------------------------------------------------
/src/question.js:
--------------------------------------------------------------------------------
1 | export class Question {
2 | static create(question) {
3 | return fetch('https://podcast--app-fd631-default-rtdb.asia-southeast1.firebasedatabase.app/questions.json', {
4 | method: "POST",
5 | body: JSON.stringify(question),
6 | headers: {
7 | 'Content-Type': 'application/json'
8 | }
9 | })
10 | .then(response => response.json())
11 | .then(response => {
12 | question.id = response.name
13 | return question
14 | })
15 | .then(addToLocalStorage)
16 | .then(Question.renderList)
17 | .catch(error => {
18 | console.log('Error', error)
19 | throw error
20 | })
21 | }
22 |
23 | static fetch(token) {
24 | if (!token) {
25 | return Promise.resolve('У вас нету токена
')
26 | }
27 | return fetch(`https://podcast--app-fd631-default-rtdb.asia-southeast1.firebasedatabase.app/questions.json?auth=${token}`)
28 | .then(response => response.json())
29 | .then(response => {
30 | if (response.error) {
31 | return `${response && response.error}
`
32 | }
33 | return response ? Object.keys(response).map(key => ({
34 | ...response[key],
35 | id: key
36 | })) : []
37 | })
38 | }
39 |
40 | static renderList() {
41 | const questions = getQuestionsFromLocalStorage()
42 |
43 | const html = questions.length ? questions.map(toCard) : `Вы пока ничего не спрашивали
`
44 |
45 | const list = document.getElementById('list')
46 | list.innerHTML = html
47 | }
48 |
49 | static listToHtml(questions) {
50 | return questions.length ? `${questions.map(q => `- ${q.text}
.join()`)}
`
51 | : 'Вопросов пока нет
'
52 | }
53 | }
54 |
55 |
56 | function addToLocalStorage(question) {
57 | question.date = new Date()
58 | const all = getQuestionsFromLocalStorage()
59 | all.push(question)
60 | localStorage.setItem('questions', JSON.stringify(all))
61 | }
62 |
63 | function getQuestionsFromLocalStorage() {
64 | return JSON.parse(localStorage.getItem('questions') || '[] ' )
65 | }
66 |
67 | function toCard(question) {
68 | return `
69 |
70 | ${new Date(question.date).toLocaleDateString()}
71 | ${new Date(question.date).toLocaleTimeString()}
72 |
73 | ${question.text}
74 |
75 | `
76 | }
--------------------------------------------------------------------------------
/public/bundle.08d1dbcd66acf7aa8ecc.js:
--------------------------------------------------------------------------------
1 | (()=>{"use strict";var t={28:(t,e,n)=>{n.d(e,{Z:()=>s});var r=n(81),o=n.n(r),a=n(645),i=n.n(a)()(o());i.push([t.id,"/**\n * Body CSS\n */\n\nhtml,\nbody {\n height: 100%;\n}\n\nhtml,\nbody,\ninput,\ntextarea,\nbutton {\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.004);\n}\n\n\n/**\n * Sidebar CSS\n */\n\n#sidebar {\n background-color: #2196f3;\n padding: 15px;\n}\n\n@media (min-width: 768px) {\n #sidebar {\n position: fixed;\n top: 0;\n bottom: 0;\n width: 180px;\n height: 100%;\n padding-top: 30px;\n }\n}\n\n.author {\n font-size: .8rem;\n}\n\n\n/**\n * Content CSS\n */\n@media (min-width: 768px) {\n #content {\n margin-left: 180px;\n }\n}\n\n.floating-btn {\n position: fixed;\n bottom: 50px;\n right: 50px;\n}\n\n.modal {\n max-width: 600px;\n max-height: 300px;\n margin: 100px auto;\n overflow-y: auto;\n background-color: #fff;\n}\n\n.modal > h1{\n text-align: center;\n}\n.modal .modal-content {\n padding: 1rem;\n}\n\n.error {\n color: red;\n}",""]);const s=i},645:t=>{t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n="",r=void 0!==e[5];return e[4]&&(n+="@supports (".concat(e[4],") {")),e[2]&&(n+="@media ".concat(e[2]," {")),r&&(n+="@layer".concat(e[5].length>0?" ".concat(e[5]):""," {")),n+=t(e),r&&(n+="}"),e[2]&&(n+="}"),e[4]&&(n+="}"),n})).join("")},e.i=function(t,n,r,o,a){"string"==typeof t&&(t=[[null,t,void 0]]);var i={};if(r)for(var s=0;s0?" ".concat(l[5]):""," {").concat(l[1],"}")),l[5]=a),n&&(l[2]?(l[1]="@media ".concat(l[2]," {").concat(l[1],"}"),l[2]=n):l[2]=n),o&&(l[4]?(l[1]="@supports (".concat(l[4],") {").concat(l[1],"}"),l[4]=o):l[4]="".concat(o)),e.push(l))}},e}},81:t=>{t.exports=function(t){return t[1]}},379:t=>{var e=[];function n(t){for(var n=-1,r=0;r{var e={};t.exports=function(t,n){var r=function(t){if(void 0===e[t]){var n=document.querySelector(t);if(window.HTMLIFrameElement&&n instanceof window.HTMLIFrameElement)try{n=n.contentDocument.head}catch(t){n=null}e[t]=n}return e[t]}(t);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(n)}},216:t=>{t.exports=function(t){var e=document.createElement("style");return t.setAttributes(e,t.attributes),t.insert(e,t.options),e}},565:(t,e,n)=>{t.exports=function(t){var e=n.nc;e&&t.setAttribute("nonce",e)}},795:t=>{t.exports=function(t){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var e=t.insertStyleElement(t);return{update:function(n){!function(t,e,n){var r="";n.supports&&(r+="@supports (".concat(n.supports,") {")),n.media&&(r+="@media ".concat(n.media," {"));var o=void 0!==n.layer;o&&(r+="@layer".concat(n.layer.length>0?" ".concat(n.layer):""," {")),r+=n.css,o&&(r+="}"),n.media&&(r+="}"),n.supports&&(r+="}");var a=n.sourceMap;a&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),e.styleTagTransform(r,t,e.options)}(e,t,n)},remove:function(){!function(t){if(null===t.parentNode)return!1;t.parentNode.removeChild(t)}(e)}}}},589:t=>{t.exports=function(t,e){if(e.styleSheet)e.styleSheet.cssText=t;else{for(;e.firstChild;)e.removeChild(e.firstChild);e.appendChild(document.createTextNode(t))}}}},e={};function n(r){var o=e[r];if(void 0!==o)return o.exports;var a=e[r]={id:r,exports:{}};return t[r](a,a.exports,n),a.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var r in e)n.o(e,r)&&!n.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:e[r]})},n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),n.nc=void 0,(()=>{class t{static create(n){return fetch("https://podcast--app-fd631-default-rtdb.asia-southeast1.firebasedatabase.app/questions.json",{method:"POST",body:JSON.stringify(n),headers:{"Content-Type":"application/json"}}).then((t=>t.json())).then((t=>(n.id=t.name,n))).then(e).then(t.renderList).catch((t=>{throw console.log("Error",t),t}))}static fetch(t){return t?fetch(`https://podcast--app-fd631-default-rtdb.asia-southeast1.firebasedatabase.app/questions.json?auth=${t}`).then((t=>t.json())).then((t=>t.error?`${t&&t.error}
`:t?Object.keys(t).map((e=>({...t[e],id:e}))):[])):Promise.resolve('У вас нету токена
')}static renderList(){const t=r(),e=t.length?t.map(o):'Вы пока ничего не спрашивали
';document.getElementById("list").innerHTML=e}static listToHtml(t){return t.length?`${t.map((t=>`- ${t.text}
.join()`))}
`:"Вопросов пока нет
"}}function e(t){t.date=new Date;const e=r();e.push(t),localStorage.setItem("questions",JSON.stringify(e))}function r(){return JSON.parse(localStorage.getItem("questions")||"[] ")}function o(t){return`\n \n ${new Date(t.date).toLocaleDateString()}\n ${new Date(t.date).toLocaleTimeString()}\n
\n ${t.text}
\n
\n `}var a=n(379),i=n.n(a),s=n(795),d=n.n(s),c=n(569),l=n.n(c),u=n(565),p=n.n(u),f=n(216),m=n.n(f),h=n(589),v=n.n(h),b=n(28),y={};function g(t){return t.length>=10}function x(t,e){const n=document.createElement("div");n.classList.add("modal");const r=`\n ${t}
\n ${e}
\n `;n.innerHTML=r,mui.overlay("on",n)}y.styleTagTransform=v(),y.setAttributes=p(),y.insert=l().bind(null,"head"),y.domAPI=d(),y.insertStyleElement=m(),i()(b.Z,y),b.Z&&b.Z.locals&&b.Z.locals;const S=document.getElementById("modal-btn"),w=document.getElementById("form"),E=w.querySelector("#question-input");window.addEventListener("load",t.renderList);const T=w.querySelector("#submit");function L(e){e.preventDefault();const n=e.target.querySelector("button"),r=e.target.querySelector("#email").value,o=e.target.querySelector("#password").value;n.disabled=!0,function(t,e){return fetch("https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=AIzaSyDOYvjw4CQtRxLhOJZLwSK8KWDvEMrALz4",{method:"POST",body:JSON.stringify({email:t,password:e,returnSecureToken:!0}),headers:{"Content-Type":"application/json"}}).then((t=>t.json())).then((t=>t.idToken))}(r,o).then(t.fetch).then(C).then((()=>n.disabled=!1))}function C(e){"string"==typeof e?x("Error",e):x("Список вопросов",t.listToHtml(e))}w.addEventListener("submit",(function(e){if(e.preventDefault(),g(E.value)){const e={text:E.value.trim(),data:(new Date).toJSON()};T.disabled=!0,t.create(e).then((()=>{E.value="",E.className="",T.disabled=!1})).catch((t=>{console.error("Error while saving the question:",t),T.disabled=!1}))}})),S.addEventListener("click",(function(){x("Авторизация",'\n \n '),document.getElementById("auth-form").addEventListener("submit",L,{once:!0})})),E.addEventListener("input",(()=>{T.disabled=!g(E.value)}))})()})();
--------------------------------------------------------------------------------