diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 274bf37..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,47 +0,0 @@ -version: 2.1 -jobs: - build: - docker: - - image: circleci/node:6.10-browsers - environment: - version_number: $CIRCLE_BRANCH - build_number: $CIRCLE_BUILD_NUM - steps: - - checkout - - restore_cache: - key: dependency-cache-{{ checksum "package.json" }} - - - run: - name: clone plugins - command: | - prnumber=$(echo $CI_PULL_REQUEST | awk -F'/' '{print $NF}') - prdata=$(curl -X GET -u $GITHUB_USER_TOKEN:x-oauth-basic https://api.github.com/repos/project-sunbird/sunbird-generic-editor/pulls/$prnumber) - target_branch=$(echo "${prdata}" | jq -r '.base.ref') - git clone https://github.com/project-sunbird/sunbird-content-plugins.git plugins -b $target_branch - - - run: sudo npm install -g bower@1.8.0 grunt-cli@1.2.0 gulp@3.9.1 codacy-coverage - - run: npm install - - run: cd app && bower cache clean --allow-root - - run: cd app && bower install --force --allow-root - #- run: gulp clone-plugins - - run: gulp packageCorePlugins - - run: npm run plugin-build - - run: npm run build - - run: npm run test - - - run: cp ./coverage/PhantomJS*/cobertura-coverage.xml /tmp/ - - - store_artifacts: - path: /tmp/cobertura-coverage.xml - destination: cobertura-coverage.xml - - - save_cache: - key: dependency-cache-{{ checksum "package.json" }} - paths: ./node_modules - - -workflows: - version: 2.1 - build_and_test: - jobs: - - build diff --git a/.github/workflows/build-and-publish.yaml b/.github/workflows/build-and-publish.yaml new file mode 100644 index 0000000..03c64d1 --- /dev/null +++ b/.github/workflows/build-and-publish.yaml @@ -0,0 +1,108 @@ +name: Publish Sunbird-generic-editor to blob + +on: + push: + tags: + - '*' + +env: + NODE_VERSION: 10.24.1 + version_number: ${{ github.ref_name }} + build_number: ${{ github.run_number }} + CLOUD_PROVIDER: ${{ vars.CLOUD_PROVIDER }} + GCP_BUCKET: ${{ vars.GCP_BUCKET }} + AZURE_CONTAINER: ${{ vars.AZURE_CONTAINER }} + +jobs: + publish: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Cache node modules + uses: actions/cache@v3 + with: + path: node_modules + key: ${{ runner.os }}-node-${{ hashFiles('package.json') }} + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + registry-url: 'https://registry.npmjs.org/' + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential libpng-dev + + - name: Clone content plugins + run: | + git clone https://github.com/project-sunbird/sunbird-content-plugins.git plugins -b ${{ github.ref_name }} + + - name: Install global dependencies + run: | + npm install -g bower@1.8.0 grunt-cli@1.2.0 gulp@3.9.1 + + - name: Install dependencies + run: npm install --legacy-peer-deps + env: + PHANTOMJS_PLATFORM: linux + PHANTOMJS_ARCH: x64 + TMPDIR: /tmp + + - name: Install bower dependencies + working-directory: app + run: | + bower cache clean --allow-root + bower install --force --allow-root + + - name: Build and package + run: | + npm run build-npm-pkg + + - name: Setup Google Cloud SDK + if: env.CLOUD_PROVIDER == 'gcp' + uses: google-github-actions/setup-gcloud@v1 + with: + install_components: 'gsutil' + + - name: Authenticate and Upload to Google Cloud Storage + if: env.CLOUD_PROVIDER == 'gcp' + run: | + echo "${{ secrets.GCP_SERVICE_ACCOUNT_KEY }}" | base64 -d > /tmp/service-account-key.json + gcloud auth activate-service-account --key-file=/tmp/service-account-key.json + gsutil -m cp -r generic-editor/* gs://${{ env.GCP_BUCKET }}/generic-editor/ + rm -f /tmp/service-account-key.json + + - name: Upload to Azure Blob Storage + if: env.CLOUD_PROVIDER == 'azure' + uses: azure/CLI@v1 + with: + azcliversion: latest + inlineScript: | + az storage blob upload-batch \ + --account-name "${{ secrets.AZURE_STORAGE_ACCOUNT }}" \ + --account-key "${{ secrets.AZURE_STORAGE_KEY }}" \ + --destination "${{ vars.AZURE_CONTAINER }}/generic-editor" \ + --source generic-editor \ + --overwrite + +# Note: Publishing to AWS S3 is not tested. + + # - name: Configure AWS credentials + # if: env.CLOUD_PROVIDER == 'aws' + # uses: aws-actions/configure-aws-credentials@v4 + # with: + # aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + # aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # aws-region: ${{ vars.AWS_REGION || 'us-east-1' }} + + # - name: Upload to Amazon S3 + # if: env.CLOUD_PROVIDER == 'aws' + # env: + # S3_BUCKET: ${{ vars.S3_BUCKET }} + # run: | + # aws s3 sync generic-editor s3://${{ env.S3_BUCKET }}/generic-editor/ \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile index b738c6e..281e3cf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -20,43 +20,29 @@ node() { sh "cd plugins && git checkout origin/${branch_name} -b ${branch_name}" } else { def scmVars = checkout scm - checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/${params.github_release_tag}"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]] - artifact_version = params.github_release_tag + checkout scm: [$class: 'GitSCM', branches: [[name: "${params.github_release_tag}"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]] commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() - branch_name = params.github_release_tag + artifact_version = params.github_release_tag + "_" + commit_hash + branch_name = params.github_release_tag.split('_')[0].split('\\.')[0] + "." + params.github_release_tag.split('_')[0].split('\\.')[1] println(ANSI_BOLD + ANSI_YELLOW + "github_release_tag specified, building from github_release_tag: " + params.github_release_tag + ANSI_NORMAL) sh "git clone https://github.com/project-sunbird/sunbird-content-plugins.git plugins" sh """ cd plugins - checkout_tag=\$(git ls-remote --tags origin release-* | grep -o 'release-.*' | sort -V | tail -n1) + checkout_tag=\$(git ls-remote --tags origin $branch_name* | grep -o "$branch_name.*" | sort -V | tail -n1) git checkout tags/\${checkout_tag} -b \${checkout_tag} """ } echo "artifact_version: " + artifact_version stage('Build') { - sh """ - export version_number=${branch_name} - export build_number=${commit_hash} - rm -rf generic-editor - node -v - npm -v - npm install - cd app - bower cache clean - bower install --force - cd .. - gulp packageCorePlugins - npm run plugin-build - npm run build - #gulp build - npm run test - """ + run_testcase = false + sh('chmod 777 build.sh') + sh("bash ./build.sh ${branch_name} ${commit_hash} ${run_testcase}") } - stage('Publish_test_results') { - cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'coverage/PhantomJS*/cobertura-coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false - } + // stage('Publish_test_results') { + // cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'coverage/PhantomJS*/cobertura-coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false + // } stage('ArchiveArtifacts') { sh """ diff --git a/README.md b/README.md index 206f87c..aabadd1 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,17 @@ [![npm version](https://badge.fury.io/js/%40project-sunbird%2Fgeneric-editor.svg)](https://badge.fury.io/js/%40project-sunbird%2Fgeneric-editor) [![Build Status](https://travis-ci.org/project-sunbird/sunbird-generic-editor.svg?branch=release-1.11.0)](https://travis-ci.org/project-sunbird/sunbird-generic-editor) ## Introduction +The generic editor is used to create contents which can be uploaded as files(H5P, epub, PDF, HTML, Youtube, Video). -Generic editor for all non-ECML contents (H5P, epub, PDF, HTML, Youtube, Video). +## Step 1: Installation + Download the content editor using the following command: +```red +Run npm i @project-sunbird/generic-editor +``` +## Step 2: Configure the generic editor + +**Required configuration** ```js window.context = { @@ -89,12 +97,13 @@ window.config = { ], corePlugins: [], corePluginMapping: {}, - useProxyForURL: false + useProxyForURL: false, + cloudStorage: {} } ``` -| Property Name | Description | Default Value | -| --- | --- | --- | +| Property Name | Description | Default Value | Example | +| --- | --- | --- | --- | | `user` | It is a `object`, Which should contain the user details(userId, name) | NA | | `sid` | It is a `string`, Session identifier | NA | | `contentId ` | It is a `string`, content identifier | NA | @@ -107,10 +116,10 @@ window.config = { | `pluginRepo ` | It is a `string`, From which location plugins should load | /plugins | | `dispatcher ` | It is a `string`,Where the telemetry should log ex(console, piwik, library, local) | console | | `plugins ` | It is a `array`, Array of plugins ex:`[{id:"org.sunbird.header",ver:"1.0",type:"plugin"}]`| NA | +| `cloudStorage` | It is `object` and which defines cloud storage configuration which contains provider & presigned_headers for diff service provider for example: Azure, AWS | ``` cloudStorage: { provider: azure, presigned_headers: { 'x-ms-blob-type': 'BlockBlob' // This header is specific to azure storage provider. } } ``` | The default configuration can be overwrite by passing empty headers. ***For example:*** If you don't want to pass any headers for AWS than pass as empty headers as below: ``` cloudStorage: { provider: azure, presigned_headers: { } } ``` - +## Step 3: Integration ```js - openGenericEditor() { jQuery.fn.iziModal = iziModal; jQuery('#genericEditor').iziModal({ @@ -133,14 +142,17 @@ window.config = { }); ``` -## How to setup sunbird-generic-editor in local +# How to setup sunbird-generic-editor in local 1. Clone this sunbird-generic-editor repo from [here](https://github.com/project-sunbird/sunbird-generic-editor) 2. Clone the sunbird-content-plugins repo from [here](https://github.com/project-sunbird/sunbird-content-plugins) 3. Go to the root directory sunbird-generic-editor. 4. Run `npm install` to install node modules. 5. `cd app` and run `bower install` to install bower components -6. Create a symlink to 'sunbird-content-plugins' (`ln -s ../sunbird-content-plugins plugins`) +6. Create a symlink to 'sunbird-content-plugins' (`ln -s ../sunbird-content-plugins plugins`) >On Windows: use `mklink` +7. Configure the genric editor [here](https://github.com/project-sunbird/sunbird-genric-editor#how-to-configure-the-sunbird-generic-editor) +7. Run `node app` +8. Open Chrome and visit this link: http://localhost:3000/app ## ChangeLogs @@ -150,7 +162,6 @@ window.config = { >For sunbird-generic-editor demo please visit [here](https://staging.open-sunbird.org/workspace/content/create) - ## License This project is licensed under the MIT License - see the [LICENSE](https://github.com/project-sunbird/sunbird-generic-editor/blob/master/LICENSE) file for details @@ -158,4 +169,4 @@ This project is licensed under the MIT License - see the [LICENSE](https://githu We use [SemVer](https://semver.org/) for versioning. For the versions available, see the [tags](https://github.com/project-sunbird/sunbird-generic-editor/tags) on this repository. ## Any Issues? -We have an open and active [issue tracker](https://project-sunbird.atlassian.net/issues/). Please report any issues. \ No newline at end of file +We have an open and active [issue tracker](https://project-sunbird.atlassian.net/issues/). Please report any issues. diff --git a/app/bower.json b/app/bower.json index 2eeb74c..3c4f0d5 100644 --- a/app/bower.json +++ b/app/bower.json @@ -19,7 +19,7 @@ "oclazyload": "^1.0.9", "ngSafeApply": "./libs/Scope.SafeApply.js", "izitoast": "^1.1.3", - "contenteditor": "https://sunbirddev.blob.core.windows.net/sunbird-content-dev/content-editor/scripts/base-editor.min.js" + "contenteditor": "https://raw.githubusercontent.com/Sunbird-Knowlg/sunbird-generic-editor/refs/heads/release-8.1.0/base-editor.min.js" }, "appPath": "app", "moduleName": "editorApp", diff --git a/app/scripts/dev/localhost-ce.js b/app/scripts/dev/localhost-ce.js index 35374f3..9da4537 100644 --- a/app/scripts/dev/localhost-ce.js +++ b/app/scripts/dev/localhost-ce.js @@ -13,5 +13,23 @@ window.config = { baseURL: "", previewURL: "/preview/preview.html", apislug: "/action", - dispatcher: "local" + dispatcher: "local", + cloudStorage: { + "presigned_headers": { + 'x-ms-blob-type': 'BlockBlob' // This header is specific to azure storage provider. + /* TODO: if more configurations comes for cloud service provider + than we have do in more generic way like below: + For example: + cloudStorage: { + provider: 'azure' // azure, aws, etc.. + azure: { + url: 'https://www.azureblogstorage.com' + presigned_headers: { + x-ms-blob-type: 'BlockBlob' + } + } + } + */ + } + } } \ No newline at end of file diff --git a/app/scripts/genericeditor/genericeditor-config.js b/app/scripts/genericeditor/genericeditor-config.js index 04de44d..1476f46 100644 --- a/app/scripts/genericeditor/genericeditor-config.js +++ b/app/scripts/genericeditor/genericeditor-config.js @@ -3,7 +3,9 @@ org.ekstep.contenteditor.config = _.assign(org.ekstep.contenteditor.config, { plugins: [ { 'id': 'org.ekstep.sunbirdcommonheader', 'ver': '1.4', 'type': 'plugin' }, { 'id': 'org.ekstep.metadata', 'ver': '1.1', 'type': 'plugin' }, - { 'id': 'org.ekstep.sunbirdmetadata', 'ver': '1.1', 'type': 'plugin' } + { 'id': 'org.ekstep.sunbirdmetadata', 'ver': '1.1', 'type': 'plugin' }, + { 'id': 'org.ekstep.uploadlargecontent', 'ver': '1.0', 'type': 'plugin' } ], extContWhitelistedDomains: 'youtube.com,youtu.be' }) + diff --git a/auto_build_deploy b/auto_build_deploy new file mode 100644 index 0000000..ecdeab5 --- /dev/null +++ b/auto_build_deploy @@ -0,0 +1,63 @@ +@Library('deploy-conf') _ +node() { + try { + String ANSI_GREEN = "\u001B[32m" + String ANSI_NORMAL = "\u001B[0m" + String ANSI_BOLD = "\u001B[1m" + String ANSI_RED = "\u001B[31m" + String ANSI_YELLOW = "\u001B[33m" + + ansiColor('xterm') { + tag_name = env.JOB_NAME.split("/")[-1] + pre_checks() + stage('Checkout') { + cleanWs() + def scmVars = checkout scm + checkout scm: [$class: 'GitSCM', branches: [[name: "refs/tags/$tag_name"]], userRemoteConfigs: [[url: scmVars.GIT_URL]]] + commit_hash = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() + artifact_version = tag_name + branch_name = tag_name.split('_')[0].split('\\.')[0] + "." + tag_name.split('_')[0].split('\\.')[1] + run_testcase = true + sh "git clone https://github.com/project-sunbird/sunbird-content-plugins.git plugins" + sh """ + cd plugins + checkout_tag=\$(git ls-remote --tags origin $branch_name* | grep -o "$branch_name.*" | sort -V | tail -n1) + git checkout tags/\${checkout_tag} -b \${checkout_tag} + """ + } + echo "artifact_version: " + artifact_version + + // stage Build + sh('chmod 777 build.sh') + sh("./build.sh ${branch_name} ${commit_hash} ${run_testcase}") + + + // stage Publish_test_results + cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'coverage/PhantomJS*/cobertura-coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false + + + // stage ArchiveArtifacts + sh """ + mkdir generic-editor-artifacts + cp generic-editor.zip generic-editor-artifacts + zip -j generic-editor-artifacts.zip:${artifact_version} generic-editor-artifacts/* + """ + archiveArtifacts "generic-editor-artifacts.zip:${artifact_version}" + sh """echo {\\"artifact_name\\" : \\"generic-editor-artifacts.zip\\", \\"artifact_version\\" : \\"${artifact_version}\\", \\"node_name\\" : \\"${env.NODE_NAME}\\"} > metadata.json""" + archiveArtifacts artifacts: 'metadata.json', onlyIfSuccessful: true + currentBuild.description = "${artifact_version}" + + } + currentBuild.result = "SUCCESS" + slack_notify(currentBuild.result, tag_name) + email_notify() + auto_build_deploy() + } + catch (err) { + currentBuild.result = "FAILURE" + slack_notify(currentBuild.result, tag_name) + email_notify() + throw err + } + +} \ No newline at end of file diff --git a/base-editor.min.js b/base-editor.min.js new file mode 100644 index 0000000..a8e766f --- /dev/null +++ b/base-editor.min.js @@ -0,0 +1 @@ +Array.prototype.find||Object.defineProperty(Array.prototype,"find",{value:function(e){if(null==this)throw new TypeError('"this" is null or not defined');var t=Object(this),r=t.length>>>0;if("function"!=typeof e)throw new TypeError("predicate must be a function");for(var i=arguments[1],a=0;a>>0;if("function"!=typeof e)throw new TypeError;for(var i=[],a=arguments.length>=2?arguments[1]:void 0,n=0;n>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");for(arguments.length>1&&(t=arguments[1]),r=0;r>>0;if("function"!=typeof e)throw new TypeError;for(arguments.length>1&&(r=t),i=0;i>>0;if(0===a)return-1;var n=0|t;if(n>=a)return-1;for(r=Math.max(n>=0?n:a-Math.abs(n),0);r>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");for(arguments.length>1&&(t=arguments[1]),r=new Array(n),i=0;i>>0;if(0===a)return-1;for(t=a-1,arguments.length>1&&((t=Number(arguments[1]))!=t?t=0:0!=t&&t!=1/0&&t!=-1/0&&(t=(t>0||-1)*Math.floor(Math.abs(t)))),r=t>=0?Math.min(t,a-1):a-Math.abs(t);r>=0;r--)if(r in i&&i[r]===e)return r;return-1}),Array.prototype.reduce||Object.defineProperty(Array.prototype,"reduce",{value:function(e){if(null===this)throw new TypeError("Array.prototype.reduce called on null or undefined");if("function"!=typeof e)throw new TypeError(e+" is not a function");var t,r=Object(this),i=r.length>>>0,a=0;if(arguments.length>=2)t=arguments[1];else{for(;a=i)throw new TypeError("Reduce of empty array with no initial value");t=r[a++]}for(;a>>0,a=i-1;if(arguments.length>=2)t=arguments[1];else{for(;a>=0&&!(a in r);)a--;if(a<0)throw new TypeError("Reduce of empty array with no initial value");t=r[a--]}for(;a>=0;a--)a in r&&(t=e(t,r[a],a,r));return t}),Array.isArray||(Array.isArray=function(e){return"[object Array]"===Object.prototype.toString.call(e)}),"function"!=typeof Object.assign&&Object.defineProperty(Object,"assign",{value:function(e,t){"use strict";if(null==e)throw new TypeError("Cannot convert undefined or null to object");for(var r=Object(e),i=1;i3?i.splice(3,i.length-1):[],void 0!==this.listeners[e]?this.listeners[e].push({scope:r,callback:t,args:i}):this.listeners[e]=[{scope:r,callback:t,args:i}]},removeEventListener:function(e,t,r){if(void 0!==this.listeners[e]){for(var i=this.listeners[e].length,a=[],n=0;n0;for(var a=0;a2?i.splice(2,i.length-1):[],i=[r].concat(i),void 0!==this.listeners[e]){var o=this.listeners[e].length;for(n=0;ns||d.hasOwnProperty(s)&&(l[d[s]]=s);a=l[r]?"keydown":"keypress"}return"keypress"==a&&o.length&&(a="keydown"),{key:i,modifiers:o,action:a}}function s(e,r){return null!==e&&e!==t&&(e===r||s(e.parentNode,r))}function c(e){function r(e){e=e||{};var t,r=!1;for(t in g)e[t]?r=!0:g[t]=0;r||(y=!1)}function s(e,t,r,i,a,o){var s,c,l=[],d=r.type;if(!h._callbacks[e])return[];for("keyup"==d&&n(e)&&(t=[e]),s=0;s":".","?":"/","|":"\\"},h={option:"alt",command:"meta",return:"enter",escape:"esc",plus:"+",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"};for(r=1;20>r;++r)d[111+r]="f"+r;for(r=0;9>=r;++r)d[r+96]=r;c.prototype.bind=function(e,t,r){return e=e instanceof Array?e:[e],this._bindMultiple.call(this,e,t,r),this},c.prototype.unbind=function(e,t){return this.bind.call(this,e,(function(){}),t)},c.prototype.trigger=function(e,t){return this._directMap[e+":"+t]&&this._directMap[e+":"+t]({},e),this},c.prototype.reset=function(){return this._callbacks={},this._directMap={},this},c.prototype.stopCallback=function(e,t){return!(-1<(" "+t.className+" ").indexOf(" mousetrap ")||s(t,this.target))&&("INPUT"==t.tagName||"SELECT"==t.tagName||"TEXTAREA"==t.tagName||t.isContentEditable)},c.prototype.handleKey=function(){return this._handleKey.apply(this,arguments)},c.addKeycodes=function(e){for(var t in e)e.hasOwnProperty(t)&&(d[t]=e[t]);l=null},c.init=function(){var e,r=c(t);for(e in r)"_"!==e.charAt(0)&&(c[e]=function(e){return function(){return r[e].apply(r,arguments)}}(e))},c.init(),e.Mousetrap=c,"undefined"!=typeof module&&module.exports&&(module.exports=c),"function"==typeof define&&define.amd&&define((function(){return c}))}}("undefined"!=typeof window?window:null,"undefined"!=typeof window?document:null),window.org={ekstep:{}};var plugin_framework=function(){};plugin_framework.prototype.initialize=function(e){if(e=e||{},org.ekstep.pluginframework.config=org.ekstep.pluginframework.config||{},!e.env)throw"Framework should be initialized with environment!";org.ekstep.pluginframework.env=e.env,org.ekstep.pluginframework.jQuery=e.jQuery||window.$,org.ekstep.pluginframework.async=e.async||window.async,org.ekstep.pluginframework.config.build_number=e.build_number||"BUILD_NUMBER",org.ekstep.pluginframework.config.pluginRepo=e.pluginRepo||"/content-plugins"},window.org.ekstep.pluginframework=new plugin_framework,plugin_framework=void 0;var services_framework=function(){};window.org.ekstep.services=new services_framework,services_framework=void 0,org.ekstep.pluginframework.resourceManager=new(Class.extend({init:function(){},buildNumber:void 0,registeredRepos:[],discoverManifest:function(e,t,r,i){var a=[];"number"==typeof t&&(t=t.toFixed(1)),this.registeredRepos.forEach((function(r,n){a.push(0===n?function(a){r.discoverManifest(e,t,a,i)}:function(a,n){void 0===a.manifest?r.discoverManifest(e,t,n,i):n(null,a)})})),org.ekstep.pluginframework.async.waterfall(a,(function(e,t){t&&void 0!==t.manifest?r(void 0,t):r("Plugin not found in any repo or manifest",void 0)}))},addRepo:function(e,t){this.registeredRepos.find((function(t){return t.id===e.id}))?console.error(e.id+": Repo already registered!"):"number"==typeof t?this.registeredRepos.splice(t,0,e):this.registeredRepos.push(e)},getResource:function(e,t,r,i,a,n,o){var s=a.resolveResource(e,t,r);this.loadResource(s,i,n,o)},loadExternalPluginResource:function(e,t,r,i,a,n,o){var s=a.resolveResource(t,r,i);this.loadExternalResource(s,e,n,o)},loadExternalResource:function(e,t,r,i){switch(t){case"js":i?this.loadResource(e,"script",i,r):(e=e+"?"+(org.ekstep.pluginframework.config?org.ekstep.pluginframework.config.build_number:""),r&&(e=e+"&"+r),org.ekstep.pluginframework.jQuery("body").append($("