diff --git a/.eslintrc.json b/.eslintrc.json index ce496cb3e45..8948a31958e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -251,6 +251,28 @@ ] } }, + { + "files": [ "plugins/content/api/positioning/**/*.js" ], + "parserOptions": { + "ecmaVersion": 2023, + "sourceType": "module" + }, + "env": { + "node": true, + "es2023": true + } + }, + { + "files": [ "plugins/content/api/positioning/**/*.cjs" ], + "parserOptions": { + "ecmaVersion": 2023, + "sourceType": "commonjs" + }, + "env": { + "node": true, + "es2023": true + } + }, { "files": [ "api/**/*.js", diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b8ccc05252a..73a66a78ecc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -6,7 +6,7 @@ name: Deploy on: # Triggers the workflow on push or pull request events but only for the master branch push: - branches: [ master, next ] + branches: [ master, next, release.24.10, release.24.12 ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: diff --git a/.github/workflows/stable-je-deploy.yml b/.github/workflows/stable-je-deploy.yml new file mode 100644 index 00000000000..6393bd70357 --- /dev/null +++ b/.github/workflows/stable-je-deploy.yml @@ -0,0 +1,28 @@ +# This is a basic workflow to help you get started with Actions + +name: Deploy Journey Engine + +# Controls when the workflow will run +on: + # Triggers the workflow on push or pull request events but only for the master branch + push: + branches: [ next ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + deploy: + runs-on: ubuntu-latest + + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v3 + + - name: Deploy server + shell: bash + env: + SSH_PRIVATE_KEY: ${{ secrets.STABLE_JE_SSH_PRIVATE_KEY }} + run: bash ./bin/scripts/deploy-je.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index dac4e84a6cf..7a4991cf7ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,19 +1,34 @@ +## Version 25.x.x +Features: +- [alerts] alerts table default order should be by creation time newest at the top +- [core] allow tracking Countly dashboard usage with Countly +- [sdk] Improved and added new Server Config options + +Enterprise Features: +- [journey_engine] Editing/Deleting/Duplication of blocks and version management + ## Version 24.12 Features: - [audit-logs] Exported audit logs from UI now would have "BEFORE" and "AFTER" fields - [core] Ability to mark reports as 'dirty' to make sure they are regenerated in full - [core] Adding a cancel button to "create new app" form - [core] Adding a nightly job to delete old data +- [core] Fixed a bug causing events to not being loaded when there's an escaped character in the event name - [core] Redirecting user to a newly created app - [core] Removing HTML from localization files - [core] Showing a flex banner on sidebar if the version is Countly Lite - [crashes] Adding confirmation for deleting crash groups - [dashoards] Fixed the "Add/manage notes" button that did not work for the technology widget - [dbviewer] Preventing aggregation of using any stages which might open user to harmful actions (like $merge, $out, $lookup, $uninonWith) for all users except global admin +- [gridfs] fixes for moving to Promises - [nps] Fixing issues with default logo selection - [populator] Adding ability to select features to populate and other small improvements +- [push] Fixed bug where IOS credentials get mixed up while sending messages from different apps at the same time +- [push] Fixed bug where it crashes in connection pool growth because of a type mismatch in an if condition +- [reports] Fixes report generation failure due to SSL error - [star-rating] Removed unnecessary limitation with using cohorts for targeting -- [surveys] Removed unnecessary limitation with using cohorts for targeting +- [system-utility] Fixed: Mongo error (code: 26) in some Countly instances when the profiler gets run for the first time +- [user-management] Global admins can now disable 2FA for individual users Enterprise Features: - [cohorts] Adding ability to edit cohorts. This deletes historical calculations @@ -21,18 +36,112 @@ Enterprise Features: - [core] Adding support For SingleStore Kai - [flows] Adding UX improvements to the editor - [journey_engine] Adding "Journey Engine" feature +- [ldap] Fixed issues that would lead to configuration options not being picked up - [remote-config] Moving enable/disable functionality to the dropdown +- [surveys] "Select one" text in the widget can be edited now +- [surveys] Removed unnecessary limitation with using cohorts for targeting Dependencies: -- Bump countly-sdk-nodejs from 22.6.0 to 24.10.0 -- Bump countly-sdk-web from 24.4.1 to 24.11.0 +- Bump countly-sdk-nodejs from 24.10.0 to 24.10.1 +- Bump countly-sdk-web from 24.11.2 to 24.11.3 +- Bump express from 4.21.1 to 4.21.2 +- Bump express-rate-limit from 7.4.1 to 7.5.0 - Bump form-data from 4.0.0 to 4.0.1 - Bump jimp from 0.22.12 to 1.6.0 - Bump jsdoc from 4.0.3 to 4.0.4 +- Bump mocha from 10.2.0 to 10.8.2 - Bump nodemailer from 6.9.15 to 6.9.16 -- Bump puppeteer from 23.8.0 to 23.9.0 +- Bump puppeteer from 23.10.4 to 23.11.1 +- Bump sass from 1.81.0 to 1.83.3 - Bump tslib from 2.7.0 to 2.8.1 +## Version 24.10.7 +Fixes: +- [data-manager] Modifying existing values when segment values want to be updated in the Data Manager +- [drill] Fix for UI error when push plugin is not enabled + +Enterprise fixes: +- [drill] Fixed empty events list in drill section + +Features: +- [core] Add self tracking capability +- [hooks] Added remote config changes to internal actions +- [system-utility] New endpoint: /take-heap-snapshot. +- [system-utility] Using nodejs fs to write profiler files instead of gridfs. + +## Version 24.10.6 +Fixes: +- [push] Using apns-id header as message result in debug mode +- [server-stats] Fix data point calculation in job +- [TopEventsJob] preserver previous state if overwriting fails +- [ui] scroll top on step changes in drawers + +Enterprise fixes: +- [drill] Encoding url component before changing history state +- [drill] Fixed drill meta regeneration +- [drill] [license] Update license loader to enable supplying db client +- [users] Format data points displayed in user sidebar +- [cohorts] Unescape drill texts in cohort component + +Dependencies: +- Bump fs-extra from 11.2.0 to 11.3.0 +- Bump nodemailer from 6.9.16 to 6.10.0 + +Enterprise Dependencies: +- Bump nanoid in /plugins/cognito from 2.1.11 to 3.3.8 +- Bump shortid in /plugins/cognito from 2.2.16 to 2.2.17 + +## Version 24.10.3 +Fixes: +- [dashboards] Fixing issue where dashboard widgets go into single column + +Security: +- Bump puppeteer from 17.1.3 to 23.8.0 +- Bump express from 4.21.0 to 4.21.1 +- Bump sass from 1.79.4 to 1.81.0 +- Bump express-session from 1.18.0 to 1.18.1 +- Bump cross-spawn from 7.0.3 to 7.0.6 in /ui-tests +- Bump cross-spawn from 7.0.3 to 7.0.6 in /plugins/hooks + +## Version 24.10.2 +Fixes: +- [core] Correct aggregated collection cleanup on event omitting +- [core] Fixed bug where changing passwords results in the loss of the "Global Admin" role +- [core] Fixed bug where exporting incoming data logs could result in "Incorrect parameter \"data\" error +- [core] Removed use of commands which needs admin rights from report manager. +- [crash] Fixed bug in crash ingestion for scenarios where the "app version" is not a string. +- [script] Fixing bug with "delete_old_members" script that led to malformed requests + +Enterprise fixes: +- [nps] Fixed bug that showed the wrong nps preview title + +## Version 24.10.1 +Fixes: +- [core] Replaced "Users" with "Sessions" label on technology home widgets +- [push] Improved ability to observe push related errors +- [push] Replaced push plugin with an earlier version of the plugin + +Enterprise fixes: +- [cohorts] Fixed issues with nightly cleanup +- [data-manager] Fixed UI bug where rules were not visible when editing "Merge by regex" transformations +- [drill] Fixed wrong pie chart label tooltip in dashboard widget +- [flows] Fixed bug in case of null data in schema +- [license] Fixed bug with MAU type of licenses that would prevent the server from starting +- [nps] Fixed bug in the editor where the "internal name" field was not mandatory +- [nps] Fixed bug where it was possible to submit empty nps surveys +- [ratings] Fixed bug with user consent +- [ratings] Fixed UI bug where "Internal name" was not a mandatory field + +Security: +- Bumped cookie-parser from 1.4.6 to 1.4.7 +- Bumped express-rate-limit from 7.4.0 to 7.4.1 +- Bumped moment-timezone from 0.5.45 to 0.5.46 +- Bumped sass from 1.79.3 to 1.79.4 +- Fixing minor vulnerability that would allow for unauthorized file upload + +Enterprise Features: +- [block] Added a way to filter crashes by their error (stacktrace) + ## Version 24.10 Fixes: - [core] Correct aggregated collection cleanup on event omitting @@ -69,6 +178,42 @@ Enterprise Features: - [users] UI improvements - [views] Added a quick transition to drill +## Version 24.05.21 +Fixes: +- [core] Fixed a bug causing events to not being loaded when there's an escaped character in the event name +- [gridfs] fixes for moving to Promises +- [reports] Fixes report generation failure due to SSL error +- [surveys] "Select one" text in the widget can be edited now +- [system-utility] Fixed: Mongo error (code: 26) in some Countly instances when the profiler gets run for the first time + +Dependencies: +- Bump countly-sdk-nodejs from 24.10.0 to 24.10.1 +- Bump countly-sdk-web from 24.11.2 to 24.11.4 +- Bump express-rate-limit from 7.4.1 to 7.5.0 +- Bump puppeteer from 23.10.4 to 23.11.1 +- Bump sass from 1.81.0 to 1.83.4 + +## Version 24.05.20 +Fixes: +- [push] Fixed bug where IOS credentials get mixed up while sending messages from different apps at the same time +- [push] Fixed bug where it crashes in connection pool growth because of a type mismatch in an if condition + +Security: +- [cohorts] Prevent query injection on cohort creation + +Dependencies: +- Bump countly-sdk-nodejs from 22.6.0 to 24.10.0 +- Bump countly-sdk-web from 24.4.1 to 24.11.0 +- Bump express from 4.21.1 to 4.21.2 +- Bump form-data from 4.0.0 to 4.0.1 +- Bump jimp from 0.22.12 to 1.6.0 +- Bump jsdoc from 4.0.3 to 4.0.4 +- Bump mocha from 10.2.0 to 10.8.2 +- Bump mongodb from 4.9.1 to 4.17.2 +- Bump nodemailer from 6.9.15 to 6.9.16 +- Bump puppeteer from 23.8.0 to 23.9.0 +- Bump tslib from 2.7.0 to 2.8.1 + ## Version 24.05.19 Fixes: - [dashboards] Fixing issue where dashboard widgets go into single column @@ -4460,4 +4605,3 @@ This version provides several features and bugfixes to both server and SDKs. The A user of an application can only view analytics for that application and cannot edit its settings. * Added csfr protection to all methods provided through app.js. - diff --git a/Dockerfile-api b/Dockerfile-api index 41e8e6fcd4c..567f0801b6d 100644 --- a/Dockerfile-api +++ b/Dockerfile-api @@ -1,4 +1,4 @@ -FROM node:hydrogen-bullseye-slim +FROM node:iron-bookworm-slim ARG COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,logger,systemlogs,populator,reports,crashes,push,star-rating,slipping-away-users,compare,server-stats,dbviewer,times-of-day,compliance-hub,alerts,onboarding,consolidate,remote-config,hooks,dashboards,sdk,data-manager,guides # Countly Enterprise: @@ -25,15 +25,14 @@ COPY . . # install required dependencies which slim image doesn't have RUN apt-get update && \ - apt-get install -y iputils-ping procps net-tools telnet apt-transport-https curl wget git python2 make gcc g++ unzip && \ - ln -s /usr/bin/python2.7 /usr/bin/python + apt-get install -y iputils-ping procps net-tools telnet apt-transport-https curl wget git make gcc g++ unzip xz-utils RUN apt-get update && \ apt-get upgrade -y && \ cd /usr/src && \ wget https://www.python.org/ftp/python/3.8.12/Python-3.8.12.tar.xz && \ tar -xf Python-3.8.12.tar.xz && \ - apt-get install -y build-essential sudo zlib1g-dev libssl1.1 libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev && \ + apt-get install -y build-essential sudo zlib1g-dev libssl3 libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev && \ cd Python-3.8.12 && \ ./configure --enable-optimizations --enable-shared && \ make && \ @@ -51,6 +50,7 @@ RUN curl -s -L -o /tmp/tini.deb "https://github.com/krallin/tini/releases/downlo # preinstall cp -n ./api/config.sample.js ./api/config.js && \ cp -n ./frontend/express/config.sample.js ./frontend/express/config.js && \ + HOME=/tmp npm install -g npm@latest && \ HOME=/tmp npm install --unsafe-perm=true --allow-root && \ HOME=/tmp npm install argon2 --build-from-source --unsafe-perm=true --allow-root && \ ./bin/docker/preinstall.sh && \ @@ -58,7 +58,7 @@ RUN curl -s -L -o /tmp/tini.deb "https://github.com/krallin/tini/releases/downlo \ # cleanup & chown npm remove -y --no-save mocha nyc should supertest && \ - apt-get remove -y git gcc g++ make automake autoconf libtool pkg-config unzip sqlite3 && \ + apt-get remove -y git gcc g++ make automake autoconf libtool pkg-config unzip sqlite3 wget && \ apt-get install -y libgbm-dev libgbm1 gconf-service libasound2 libatk1.0-0 libatk-bridge2.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ rm -rf test /tmp/* /tmp/.??* /var/tmp/* /var/tmp/.??* /var/log/* /root/.npm && \ diff --git a/Dockerfile-centos-api b/Dockerfile-centos-api index 7a56ce89416..37c5d20f10b 100644 --- a/Dockerfile-centos-api +++ b/Dockerfile-centos-api @@ -34,7 +34,7 @@ RUN yum update -y RUN curl -s -L -o /tmp/tini.rpm "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini_${TINI_VERSION}.rpm" && \ rpm -i /tmp/tini.rpm && \ \ - curl -sL https://rpm.nodesource.com/setup_18.x | bash - && \ + curl -sL https://rpm.nodesource.com/setup_20.x | bash - && \ yum install -y nodejs python3.8 python2 python38-libs python38-devel python38-pip nss libdrm libgbm cyrus-sasl* && \ ln -s /usr/bin/node /usr/bin/nodejs && \ unlink /usr/bin/python3 && \ @@ -45,7 +45,7 @@ RUN curl -s -L -o /tmp/tini.rpm "https://github.com/krallin/tini/releases/downlo yum group install -y "Development Tools" && \ yum install -y epel-release && \ yum install -y pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc && \ - yum install -y https://pkgs.dyn.su/el8/base/x86_64/raven-release-1.0-2.el8.noarch.rpm && \ + yum install -y https://pkgs.sysadmins.ws/el8/base/x86_64/raven-release-1.0-2.el8.noarch.rpm && \ yum install -y wget openssl-devel make git libsqlite* sqlite unzip bzip2 && \ # modify standard distribution ./bin/docker/modify.sh && \ @@ -53,6 +53,7 @@ RUN curl -s -L -o /tmp/tini.rpm "https://github.com/krallin/tini/releases/downlo # preinstall cp -n ./api/config.sample.js ./api/config.js && \ cp -n ./frontend/express/config.sample.js ./frontend/express/config.js && \ + HOME=/tmp npm install -g npm@latest && \ HOME=/tmp npm install --unsafe-perm=true --allow-root && \ HOME=/tmp npm install argon2 --build-from-source --unsafe-perm=true --allow-root && \ ./bin/docker/preinstall.sh && \ @@ -73,4 +74,4 @@ USER 1001:0 ENTRYPOINT ["/usr/bin/tini", "-v", "--"] -CMD ["/opt/countly/bin/docker/cmd.sh"] \ No newline at end of file +CMD ["/opt/countly/bin/docker/cmd.sh"] diff --git a/Dockerfile-centos-frontend b/Dockerfile-centos-frontend index 5817654f5c9..d49991e232d 100644 --- a/Dockerfile-centos-frontend +++ b/Dockerfile-centos-frontend @@ -32,7 +32,7 @@ RUN yum update -y RUN curl -s -L -o /tmp/tini.rpm "https://github.com/krallin/tini/releases/download/v${TINI_VERSION}/tini_${TINI_VERSION}.rpm" && \ rpm -i /tmp/tini.rpm && \ \ - curl -sL https://rpm.nodesource.com/setup_18.x | bash - && \ + curl -sL https://rpm.nodesource.com/setup_20.x | bash - && \ yum install -y nodejs python3.8 python2 python38-libs python38-devel python38-pip nss libdrm libgbm cyrus-sasl* && \ ln -s /usr/bin/node /usr/bin/nodejs && \ unlink /usr/bin/python3 && \ @@ -43,7 +43,7 @@ RUN curl -s -L -o /tmp/tini.rpm "https://github.com/krallin/tini/releases/downlo yum group install -y "Development Tools" && \ yum install -y epel-release && \ yum install -y pango.x86_64 libXcomposite.x86_64 libXcursor.x86_64 libXdamage.x86_64 libXext.x86_64 libXi.x86_64 libXtst.x86_64 cups-libs.x86_64 libXScrnSaver.x86_64 libXrandr.x86_64 GConf2.x86_64 alsa-lib.x86_64 atk.x86_64 gtk3.x86_64 xorg-x11-fonts-100dpi xorg-x11-fonts-75dpi xorg-x11-utils xorg-x11-fonts-cyrillic xorg-x11-fonts-Type1 xorg-x11-fonts-misc && \ - yum install -y https://pkgs.dyn.su/el8/base/x86_64/raven-release-1.0-2.el8.noarch.rpm && \ + yum install -y https://pkgs.sysadmins.ws/el8/base/x86_64/raven-release-1.0-2.el8.noarch.rpm && \ yum install -y wget openssl-devel make git sqlite libsqlite* unzip bzip2 && \ # modify standard distribution ./bin/docker/modify.sh && \ @@ -52,6 +52,7 @@ RUN curl -s -L -o /tmp/tini.rpm "https://github.com/krallin/tini/releases/downlo cp -n ./frontend/express/public/javascripts/countly/countly.config.sample.js ./frontend/express/public/javascripts/countly/countly.config.js && \ cp -n ./frontend/express/config.sample.js ./frontend/express/config.js && \ cp -n ./api/config.sample.js ./api/config.js && \ + HOME=/tmp npm install -g npm@latest && \ HOME=/tmp npm install --unsafe-perm=true --allow-root && \ HOME=/tmp npm install argon2 --build-from-source --unsafe-perm=true --allow-root && \ ./bin/docker/preinstall.sh && \ diff --git a/Dockerfile-core b/Dockerfile-core index 8bbf7ae4fec..f525a43cc5f 100644 --- a/Dockerfile-core +++ b/Dockerfile-core @@ -40,7 +40,7 @@ RUN useradd -r -M -U -d /opt/countly -s /bin/false countly && \ gcc g++ make binutils autoconf automake autotools-dev libtool pkg-config zlib1g-dev libcunit1-dev libssl-dev libxml2-dev libev-dev \ libevent-dev libjansson-dev libjemalloc-dev cython python3-dev python-setuptools && \ # node - wget -qO- https://deb.nodesource.com/setup_18.x | bash - && \ + wget -qO- https://deb.nodesource.com/setup_20.x | bash - && \ # data_migration (mongo clients) wget -qO - https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add - && \ echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list && \ diff --git a/Dockerfile-frontend b/Dockerfile-frontend index 66cc47a3b1b..4db8d33266f 100644 --- a/Dockerfile-frontend +++ b/Dockerfile-frontend @@ -1,4 +1,4 @@ -FROM node:hydrogen-bullseye-slim +FROM node:iron-bookworm-slim ARG COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,logger,systemlogs,populator,reports,crashes,push,star-rating,slipping-away-users,compare,server-stats,dbviewer,times-of-day,compliance-hub,alerts,onboarding,consolidate,remote-config,hooks,dashboards,sdk,data-manager,guides # Countly Enterprise: @@ -21,15 +21,14 @@ WORKDIR /opt/countly COPY . . # install required dependencies which slim image doesn't have RUN apt-get update && \ - apt-get install -y iputils-ping net-tools telnet apt-transport-https procps curl wget git python2 make gcc g++ unzip && \ - ln -s /usr/bin/python2.7 /usr/bin/python + apt-get install -y iputils-ping net-tools telnet apt-transport-https procps curl wget git make gcc g++ unzip xz-utils RUN apt-get update && \ apt-get upgrade -y && \ cd /usr/src && \ wget https://www.python.org/ftp/python/3.8.12/Python-3.8.12.tar.xz && \ tar -xf Python-3.8.12.tar.xz && \ - apt-get install -y build-essential sudo zlib1g-dev libssl1.1 libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev && \ + apt-get install -y build-essential sudo zlib1g-dev libssl3 libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev && \ cd Python-3.8.12 && \ ./configure --enable-optimizations --enable-shared && \ make && \ @@ -48,6 +47,7 @@ RUN curl -s -L -o /tmp/tini.deb "https://github.com/krallin/tini/releases/downlo cp -n ./api/config.sample.js ./api/config.js && \ cp -n ./frontend/express/config.sample.js ./frontend/express/config.js && \ cp -n ./frontend/express/public/javascripts/countly/countly.config.sample.js ./frontend/express/public/javascripts/countly/countly.config.js && \ + HOME=/tmp npm install -g npm@latest && \ HOME=/tmp npm install --unsafe-perm=true --allow-root && \ HOME=/tmp npm install argon2 --build-from-source --unsafe-perm=true --allow-root && \ ./bin/docker/preinstall.sh && \ @@ -56,7 +56,7 @@ RUN curl -s -L -o /tmp/tini.deb "https://github.com/krallin/tini/releases/downlo \ # cleanup & chown npm remove -y --no-save mocha nyc should supertest puppeteer && \ - apt-get remove -y git gcc g++ make automake autoconf libtool pkg-config unzip sqlite3 && \ + apt-get remove -y git gcc g++ make automake autoconf libtool pkg-config unzip sqlite3 wget && \ apt-get autoremove -y && \ apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ rm -rf test /tmp/* /tmp/.??* /var/tmp/* /var/tmp/.??* /var/log/* /root/.npm && \ diff --git a/README.md b/README.md index 04f9491a218..3f4ad6978b4 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ ## 🔗 Quick links * [Countly Website](https://countly.com) -* [Countly Server installation guide](https://support.count.ly/hc/en-us/articles/360036862332-Installing-the-Countly-Server) -* [Countly SDKs, download and documentation links](https://support.count.ly/hc/en-us/articles/360037236571-Downloading-and-Installing-SDKs) +* [Countly Server installation guide](https://support.countly.com/hc/en-us/articles/360036862332-Installing-the-Countly-Server) +* [Countly SDKs, download and documentation links](https://support.countly.com/hc/en-us/articles/360037236571-Downloading-and-Installing-SDKs) * [Countly Community on Discord](https://discord.gg/countly) -* [User Guides for Countly features](https://support.count.ly/hc/en-us/sections/7039354168729-User-Guides-Countly-22-x) +* [User Guides for Countly features](https://support.countly.com/hc/en-us/sections/360007405211-User-Guides) ## 🌟 What is Countly? @@ -53,8 +53,8 @@ This repository includes server-side part of Countly, with the following feature Countly can collect and visualize data from mobile, web and desktop applications. Using the write-API you can send data into Countly from any source. For more information please check the below resources: -* [List of Countly SDKs, documentation and download information](https://support.count.ly/hc/en-us/articles/360037236571-Downloading-and-Installing-SDKs) -* [SDK development guide to build your own SDK](https://support.count.ly/hc/en-us/articles/360037753291-SDK-development-guide) +* [List of Countly SDKs, documentation and download information](https://support.countly.com/hc/en-us/articles/360037236571-Downloading-and-Installing-SDKs) +* [SDK development guide to build your own SDK](https://support.countly.com/hc/en-us/articles/360037753291-SDK-development-guide) * [Countly Server Write API to send data into Countly from any source](https://api.count.ly/reference/i) ## 🛠️ Installing and upgrading Countly server @@ -69,15 +69,15 @@ There are several ways to install Countly: 2. For bash lovers, we provide a beautiful installation script (`bin/countly.install.sh`) in countly-server package which installs everything required to run Countly Server. For this, you need a stable release of this repository [available here](https://github.com/Countly/countly-server/releases). -3. Countly Lite also has Docker support - [see our official Docker repository](https://registry.hub.docker.com/r/countly/countly-server/) and [installation instructions for Docker](https://support.count.ly/hc/en-us/articles/360036862332-Installing-the-Countly-Server). +3. Countly Lite also has Docker support - [see our official Docker repository](https://registry.hub.docker.com/r/countly/countly-server/) and [installation instructions for Docker](https://support.countly.com/hc/en-us/articles/360036862332-Installing-the-Countly-Server). -If you want to upgrade Countly from a previous version, please take a look at [upgrading documentation](https://support.count.ly/hc/en-us/articles/360037443652-Upgrading-the-Countly-Server). +If you want to upgrade Countly from a previous version, please take a look at [upgrading documentation](https://support.countly.com/hc/en-us/articles/360037443652-Upgrading-the-Countly-Server). ## 🧩 API, extensibility and plugins Countly has a [well-defined API](https://api.count.ly), that reads and writes data from/to the Countly backend. Countly dashboard is built using the read API, so it's possible to fetch any information you see on the dashboard using the API. -Countly is extensible using the plugin architecture. If you would like to modify any exiting feature by extending it or changing it, or if you would like to add completely new capabilities to Countly you can modify existing plugins or create new ones. We suggest [you read this document](https://support.count.ly/hc/en-us/articles/360036862392-Introduction) if you would like to start with plugin development. +Countly is extensible using the plugin architecture. If you would like to modify any exiting feature by extending it or changing it, or if you would like to add completely new capabilities to Countly you can modify existing plugins or create new ones. We suggest [you read this document](https://support.countly.com/hc/en-us/articles/360036862392-Introduction) if you would like to start with plugin development. ## 💚 Community @@ -93,7 +93,7 @@ Security is very important to us. If you discover any issue regarding security, * **NodeJS** — An open-source, cross-platform JavaScript runtime environment * **Linux** — What we all love using ;-) -Plus lots of [open source libraries](https://support.count.ly/hc/en-us/articles/360037092232-Open-source-components)! +Plus lots of [open source libraries](https://support.countly.com/hc/en-us/articles/360037092232-Open-source-components)! ## 🤝 How can I help you with your efforts? diff --git a/api/api.js b/api/api.js index 2745ffef8ff..667d6870b5c 100644 --- a/api/api.js +++ b/api/api.js @@ -113,8 +113,8 @@ plugins.connectToAllDatabases().then(function() { password_rotation: 3, password_autocomplete: true, robotstxt: "User-agent: *\nDisallow: /", - dashboard_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nStrict-Transport-Security:max-age=31536000 ; includeSubDomains\nX-Content-Type-Options: nosniff", - api_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nAccess-Control-Allow-Origin:*", + dashboard_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nStrict-Transport-Security:max-age=31536000; includeSubDomains; preload\nX-Content-Type-Options: nosniff", + api_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nStrict-Transport-Security:max-age=31536000; includeSubDomains; preload\nAccess-Control-Allow-Origin:*", dashboard_rate_limit_window: 60, dashboard_rate_limit_requests: 500, proxy_hostname: "", diff --git a/api/jobs/topEvents.js b/api/jobs/topEvents.js index 44913509632..20d3af998ac 100644 --- a/api/jobs/topEvents.js +++ b/api/jobs/topEvents.js @@ -19,8 +19,8 @@ class TopEventsJob extends job.Job { /** * TopEvents initialize function */ - init() { - this.getAllApps(); + async init() { + return this.getAllApps(); } /** @@ -144,6 +144,7 @@ class TopEventsJob extends job.Job { } catch (error) { log.e("TopEvents Job has a error: ", error); + throw error; } } @@ -157,7 +158,18 @@ class TopEventsJob extends job.Job { const encodedData = this.encodeEvents(data); const timeSecond = this.timeSecond(); const currentPeriood = this.mutatePeriod(period); - await new Promise((res, rej) => common.db.collection(TopEventsJob.COLLECTION_NAME).insert({ app_id: _id, ts: timeSecond, period: currentPeriood, data: encodedData, totalCount: totalCount, prevTotalCount: prevTotalCount, totalSum: totalSum, prevTotalSum: prevTotalSum, totalDuration: totalDuration, prevTotalDuration: prevTotalDuration, prevSessionCount: sessionData.prevSessionCount, totalSessionCount: sessionData.totalSessionCount, prevUsersCount: usersData.prevUsersCount, totalUsersCount: usersData.totalUsersCount }, (error, records) => !error && records ? res(records) : rej(error))); + await new Promise((res, rej) => common.db.collection(TopEventsJob.COLLECTION_NAME).findOneAndReplace( + { + app_id: _id, period: currentPeriood, + }, + { + app_id: _id, ts: timeSecond, period: currentPeriood, data: encodedData, totalCount: totalCount, prevTotalCount: prevTotalCount, totalSum: totalSum, prevTotalSum: prevTotalSum, totalDuration: totalDuration, prevTotalDuration: prevTotalDuration, prevSessionCount: sessionData.prevSessionCount, totalSessionCount: sessionData.totalSessionCount, prevUsersCount: usersData.prevUsersCount, totalUsersCount: usersData.totalUsersCount + }, + { + upsert: true + }, + (error, records) => !error && records ? res(records) : rej(error)) + ); } /** @@ -169,7 +181,6 @@ class TopEventsJob extends job.Job { const getEvents = await new Promise((res, rej) => common.db.collection("events").findOne({ _id: app._id }, (errorEvents, result) => errorEvents ? rej(errorEvents) : res(result))); if (getEvents && 'list' in getEvents) { const eventMap = this.eventsFilter(getEvents.list); - await new Promise((res, rej) => common.db.collection(TopEventsJob.COLLECTION_NAME).remove({ app_id: app._id }, (error, result) => error ? rej(error) : res(result))); if (eventMap && eventMap instanceof Array) { for (const period of TopEventsJob.PERIODS) { const data = {}; @@ -211,9 +222,14 @@ class TopEventsJob extends job.Job { * @param {Db} db connection * @param {done} done callback */ - run(db, done) { - this.init(); - done(); + async run(db, done) { + try { + await this.init(); + done(); + } + catch (error) { + done(error); + } } } diff --git a/api/parts/mgmt/app_users.js b/api/parts/mgmt/app_users.js index 539149f9c13..e438db69ccb 100644 --- a/api/parts/mgmt/app_users.js +++ b/api/parts/mgmt/app_users.js @@ -509,6 +509,7 @@ usersApi.mergeOtherPlugins = function(options, callback) { if (result && result.length) { for (let index = 0; index < result.length; index++) { if (result[index].status === "rejected") { + log.e(result[index]); retry = true; break; } diff --git a/api/utils/common.js b/api/utils/common.js index af59a3ea0c8..16b00eba4c4 100644 --- a/api/utils/common.js +++ b/api/utils/common.js @@ -693,7 +693,13 @@ common.getDate = function(timestamp, timezone) { * @returns {number} current day of the year */ common.getDOY = function(timestamp, timezone) { - var endDate = (timestamp) ? moment.unix(timestamp * 1000) : moment(); + var endDate; + if (timestamp && timestamp.toString().length === 13) { + endDate = (timestamp) ? moment.unix(timestamp / 1000) : moment(); + } + else { + endDate = (timestamp) ? moment.unix(timestamp) : moment(); + } if (timezone) { endDate.tz(timezone); diff --git a/api/utils/countly-request/package-lock.json b/api/utils/countly-request/package-lock.json index 55eef687cec..57af8f88f04 100644 --- a/api/utils/countly-request/package-lock.json +++ b/api/utils/countly-request/package-lock.json @@ -13,7 +13,7 @@ "hpagent": "^1.2.0" }, "devDependencies": { - "mocha": "^10.2.0", + "mocha": "^10.8.2", "should": "^13.2.3" } }, @@ -77,10 +77,11 @@ } }, "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -132,7 +133,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -144,13 +146,13 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -303,19 +305,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -326,12 +323,6 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/decamelize": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", @@ -378,10 +369,11 @@ } }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -462,7 +454,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.2", @@ -502,20 +495,21 @@ } }, "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -533,18 +527,6 @@ "node": ">= 6" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/got": { "version": "11.8.5", "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", @@ -616,7 +598,9 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -626,7 +610,8 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -773,10 +758,11 @@ } }, "node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -784,42 +770,33 @@ "node": ">=10" } }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -827,10 +804,6 @@ }, "engines": { "node": ">= 14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" } }, "node_modules/ms": { @@ -839,18 +812,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -926,15 +887,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -972,6 +924,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -1031,13 +984,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -1162,10 +1117,11 @@ } }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -1217,10 +1173,11 @@ } }, "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } diff --git a/api/utils/countly-request/package.json b/api/utils/countly-request/package.json old mode 100755 new mode 100644 index 2ef44d17835..3c5080bdb45 --- a/api/utils/countly-request/package.json +++ b/api/utils/countly-request/package.json @@ -18,7 +18,7 @@ "hpagent": "^1.2.0" }, "devDependencies": { - "mocha": "^10.2.0", + "mocha": "^10.8.2", "should": "^13.2.3" } } diff --git a/api/utils/countlyFs.js b/api/utils/countlyFs.js index fc8ed4fb705..5568367d9c8 100644 --- a/api/utils/countlyFs.js +++ b/api/utils/countlyFs.js @@ -68,7 +68,7 @@ countlyFs.gridfs = {}; **/ function beforeSave(category, filename, options, callback, done) { log.d("checking file", filename); - ob.getId(category, filename, function(err, res) { + ob.getId(category, filename, async function(err, res) { log.d("file state", filename, err, res); if (options.forceClean) { ob.clearFile(category, filename, done); @@ -80,15 +80,20 @@ countlyFs.gridfs = {}; else if (options.writeMode === "overwrite") { var bucket = new GridFSBucket(db, { bucketName: category }); log.d("deleting file", filename); - bucket.delete(res, function(error) { - log.d("deleted", filename, error); - if (!error) { - setTimeout(done, 1); - } - else if (callback) { - callback(error); - } - }); + let errHandle = null; + try { + await bucket.delete(res); + } + catch (error) { + errHandle = error; + } + log.d("deleted", filename, errHandle); + if (!errHandle) { + setTimeout(done, 1); + } + else if (callback) { + callback(errHandle); + } } else { if (callback) { @@ -116,6 +121,7 @@ countlyFs.gridfs = {}; * }); */ ob.getId = function(category, filename, callback) { + log.d("getId", category, filename); db.collection(category + ".files").findOne({ filename: filename }, {_id: 1}, function(err, res) { if (callback) { callback(err, (res && res._id) ? res._id : false); @@ -144,6 +150,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } + log.d("exists", category, dest, options); var query = {}; if (options.id) { query._id = options.id; @@ -184,7 +191,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } - + log.d("saveFile", category, dest, source, options); var filename = dest.split(path.sep).pop(); beforeSave(category, filename, options, callback, function() { save(category, filename, fs.createReadStream(source), options, callback); @@ -218,6 +225,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } + log.d("saveData", category, dest, typeof data, options); beforeSave(category, filename, options, callback, function() { var readStream = new Readable; readStream.push(data); @@ -253,6 +261,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } + log.d("saveStream", category, dest, typeof readStream, options); beforeSave(category, filename, options, callback, function() { save(category, filename, readStream, options, callback); }); @@ -271,7 +280,7 @@ countlyFs.gridfs = {}; * console.log("Finished", err); * }); */ - ob.rename = function(category, dest, source, options, callback) { + ob.rename = async function(category, dest, source, options, callback) { var newname = dest.split(path.sep).pop(); var oldname = source.split(path.sep).pop(); if (typeof options === "function") { @@ -281,25 +290,35 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } - + log.d("rename", category, dest, source, options); if (options.id) { let bucket = new GridFSBucket(db, { bucketName: category }); - bucket.rename(options.id, newname, function(error) { - if (callback) { - callback(error); - } - }); + let errHandle = null; + try { + await bucket.rename(options.id, newname); + } + catch (error) { + errHandle = error; + } + if (callback) { + callback(errHandle); + } } else { - db.collection(category + ".files").findOne({ filename: oldname }, {_id: 1}, function(err, res) { + db.collection(category + ".files").findOne({ filename: oldname }, {_id: 1}, async function(err, res) { if (!err) { if (res && res._id) { let bucket = new GridFSBucket(db, { bucketName: category }); - bucket.rename(res._id, newname, function(error) { - if (callback) { - callback(error); - } - }); + let errHandle = null; + try { + await bucket.rename(res._id, newname); + } + catch (error) { + errHandle = error; + } + if (callback) { + callback(errHandle); + } } else { if (callback) { @@ -391,7 +410,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } - + log.d("deleteFile", category, dest, options); if (options.id) { ob.deleteFileById(category, options.id, callback); } @@ -426,13 +445,19 @@ countlyFs.gridfs = {}; * console.log("Finished", err); * }); */ - ob.deleteAll = function(category, dest, callback) { + ob.deleteAll = async function(category, dest, callback) { + log.d("deleteAll", category, dest); var bucket = new GridFSBucket(db, { bucketName: category }); - bucket.drop(function(error) { - if (callback) { - callback(error); - } - }); + let errHandle = null; + try { + await bucket.drop(); + } + catch (error) { + errHandle = error; + } + if (callback) { + callback(errHandle); + } }; /** @@ -457,7 +482,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } - + log.d("getStream", category, dest, options); if (callback) { if (options.id) { ob.getStreamById(category, options.id, callback); @@ -490,7 +515,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } - + log.d("getData", category, dest, options); if (options.id) { ob.getDataById(category, options.id, callback); } @@ -536,7 +561,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } - + log.d("getSize", category, dest, options); var query = {}; if (options.id) { query._id = options.id; @@ -571,7 +596,7 @@ countlyFs.gridfs = {}; if (!options) { options = {}; } - + log.d("getStats", category, dest, options); var query = {}; if (options.id) { query._id = options.id; @@ -608,6 +633,7 @@ countlyFs.gridfs = {}; * }); */ ob.getDataById = function(category, id, callback) { + log.d("getDataById", category, id); var bucket = new GridFSBucket(db, { bucketName: category }); var downloadStream = bucket.openDownloadStream(id); downloadStream.on('error', function(error) { @@ -639,6 +665,7 @@ countlyFs.gridfs = {}; * }); */ ob.getStreamById = function(category, id, callback) { + log.d("getStreamById", category, id); if (callback) { var bucket = new GridFSBucket(db, { bucketName: category }); callback(null, bucket.openDownloadStream(id)); @@ -656,17 +683,17 @@ countlyFs.gridfs = {}; * }); */ ob.deleteFileById = async function(category, id, callback) { + log.d("deleteFileById", category, id); var bucket = new GridFSBucket(db, { bucketName: category }); + let errHandle = null; try { await bucket.delete(id); - if (callback) { - callback(null); - } } - catch (ee) { - if (callback) { - callback(ee); - } + catch (error) { + errHandle = error; + } + if (callback) { + callback(errHandle); } }; @@ -681,6 +708,7 @@ countlyFs.gridfs = {}; * }); */ ob.clearFile = function(category, filename, callback) { + log.d("clearFile", category, filename); db.collection(category + ".files").deleteMany({ filename: filename }, function(err1, res1) { log.d("deleting files", category, { filename: filename }, err1, res1 && res1.result); db.collection(category + ".chunks").deleteMany({ files_id: filename }, function(err2, res2) { @@ -697,6 +725,7 @@ countlyFs.gridfs = {}; * @param {function} callback - function called when files found or query errored, providing error object as first param and a list of filename, creation date and size as secondas second */ ob.listFiles = function(category, callback) { + log.d("listFiles", category); const bucket = new GridFSBucket(db, { bucketName: category }); bucket.find().toArray() .then((records) => callback( diff --git a/api/utils/render.js b/api/utils/render.js index ba65f9d11cc..ee01d946878 100644 --- a/api/utils/render.js +++ b/api/utils/render.js @@ -67,7 +67,7 @@ exports.renderView = function(options, cb) { XDG_CONFIG_HOME: pathModule.resolve(__dirname, "../../.cache/chrome/tmp/.chromium"), XDG_CACHE_HOME: pathModule.resolve(__dirname, "../../.cache/chrome/tmp/.chromium") }, - args: ['--no-sandbox', '--disable-setuid-sandbox'], + args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'], ignoreHTTPSErrors: true, userDataDir: pathModule.resolve(__dirname, "../../dump/chrome") }; diff --git a/api/utils/requestProcessor.js b/api/utils/requestProcessor.js old mode 100755 new mode 100644 index a314ef9facb..cee4ca4c9fd --- a/api/utils/requestProcessor.js +++ b/api/utils/requestProcessor.js @@ -3265,6 +3265,80 @@ const processFetchRequest = (params, app, done) => { }); }; +/** + * Process Bulk Request + * @param {number} i - request number in bulk + * @param {array} requests - array of requests to process + * @param {params} params - params object + * @returns {void} void + */ +const processBulkRequest = (i, requests, params) => { + const appKey = params.qstring.app_key; + if (i === requests.length) { + common.unblockResponses(params); + if ((params.qstring.safe_api_response || plugins.getConfig("api", params.app && params.app.plugins, true).safe) && !params.res.finished) { + common.returnMessage(params, 200, 'Success'); + } + return; + } + + if (!requests[i] || (!requests[i].app_key && !appKey)) { + return processBulkRequest(i + 1, requests, params); + } + if (params.qstring.safe_api_response) { + requests[i].safe_api_response = true; + } + params.req.body = JSON.stringify(requests[i]); + const tmpParams = { + 'app_id': '', + 'app_cc': '', + 'ip_address': requests[i].ip_address || common.getIpAddress(params.req), + 'user': { + 'country': requests[i].country_code || 'Unknown', + 'city': requests[i].city || 'Unknown' + }, + 'qstring': requests[i], + 'href': "/i", + 'res': params.res, + 'req': params.req, + 'promises': [], + 'bulk': true, + 'populator': params.qstring.populator, + 'blockResponses': true + }; + + tmpParams.qstring.app_key = (requests[i].app_key || appKey) + ""; + + if (!tmpParams.qstring.device_id) { + return processBulkRequest(i + 1, requests, params); + } + else { + //make sure device_id is string + tmpParams.qstring.device_id += ""; + tmpParams.app_user_id = common.crypto.createHash('sha1') + .update(tmpParams.qstring.app_key + tmpParams.qstring.device_id + "") + .digest('hex'); + } + + return validateAppForWriteAPI(tmpParams, () => { + /** + * Dispatches /sdk/end event upon finishing processing request + **/ + function resolver() { + plugins.dispatch("/sdk/end", {params: tmpParams}, () => { + processBulkRequest(i + 1, requests, params); + }); + } + + Promise.all(tmpParams.promises) + .then(resolver) + .catch((error) => { + console.log(error); + resolver(); + }); + }); +}; + /** * @param {object} params - params object * @param {String} type - source type diff --git a/bin/commands/scripts/apidocs.sh b/bin/commands/scripts/apidocs.sh index 60c9fda72b6..21b079a780f 100644 --- a/bin/commands/scripts/apidocs.sh +++ b/bin/commands/scripts/apidocs.sh @@ -12,5 +12,7 @@ then elif [ "$1" = "generate" ]; then echo 'yes' echo "$DIR/../../../../plugins/" + npm install apidoc; + npm install apidoc-template; "$DIR/../../../node_modules/.bin/apidoc" -c "$DIR/../../../apidoc.json" -f "api/.*\\.js$" -i "$DIR/../../../plugins/" -o "$DIR/../../../frontend/express/public/apidoc/" -t "$DIR/../../../node_modules/apidoc-template/template/"; fi diff --git a/bin/commands/scripts/docs.sh b/bin/commands/scripts/docs.sh index 9db21208285..a2fb5f6c1de 100644 --- a/bin/commands/scripts/docs.sh +++ b/bin/commands/scripts/docs.sh @@ -20,7 +20,7 @@ elif [ "$1" = "generate" ]; then npx jsdoc "$DIR/../../../frontend/express/app.js" "$DIR/../../../frontend/express/config.sample.js" "$DIR/../../../frontend/express/version.info.js" "$DIR/../../../frontend/express/locale.conf.js" "$DIR/../../../frontend/express/libs/" -R "$DIR/../../../README.md" -c "$DIR/../../../jsdoc_conf.json" -d "$DIR/../../../frontend/express/public/docs/app" ; #apidoc - npx apidoc -i "$DIR/../../../" -o "$DIR/../../../frontend/express/public/docs/apidoc" -f ".*\\.js$" -e "node_modules" ; + npm install apidoc; npm install apidoc-template; npx apidoc -i "$DIR/../../../" -o "$DIR/../../../frontend/express/public/docs/apidoc" -f ".*\\.js$" -e "node_modules" ; #add redirect for main folder echo "" > "$DIR/../../../frontend/express/public/docs/index.html" diff --git a/bin/countly.install_rhel.sh b/bin/countly.install_rhel.sh index ba4577a1b47..0b135def439 100644 --- a/bin/countly.install_rhel.sh +++ b/bin/countly.install_rhel.sh @@ -48,9 +48,9 @@ cp "$DIR/config/supervisord.example.conf" "$DIR/config/supervisord.conf" #Install raven-release for ipa-gothic-fonts required by puppeteer if [[ "$CENTOS_MAJOR" = "9" ]]; then - sudo rpm -ivh https://pkgs.dyn.su/el8/base/x86_64/ipa-gothic-fonts-003.03-15.el8.noarch.rpm + sudo rpm -ivh https://pkgs.sysadmins.ws/el8/base/x86_64/ipa-gothic-fonts-003.03-15.el8.noarch.rpm else - sudo yum install -y https://pkgs.dyn.su/el8/base/x86_64/raven-release-1.0-3.el8.noarch.rpm + sudo yum install https://pkgs.sysadmins.ws/el8/base/x86_64/raven-release-1.0-3.el8.noarch.rpm sudo yum install -y ipa-gothic-fonts fi diff --git a/bin/scripts/deploy-je.sh b/bin/scripts/deploy-je.sh new file mode 100644 index 00000000000..267308def03 --- /dev/null +++ b/bin/scripts/deploy-je.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Generate a timestamp for the log file +TIMESTAMP=$(date +"%Y%m%d_%H%M%S") +LOG_FILE="deploy_$TIMESTAMP.log" + +# Check if the event is a push to the "next" branch in the "Countly/countly-server" repository +if [ -z "$GITHUB_HEAD_REF" ] && [ "$GITHUB_REPOSITORY" == "Countly/countly-server" ]; then + GITHUB_BRANCH=${GITHUB_REF#refs/heads/} + echo "$GITHUB_BRANCH" + if [ "$GITHUB_BRANCH" == "next" ]; then + echo "$SSH_PRIVATE_KEY" > je-deploy-key + mkdir -p ~/.ssh + mv je-deploy-key ~/.ssh/id_rsa + chmod 600 ~/.ssh/id_rsa + + # Log the deployment command with a timestamped log file + ssh -oStrictHostKeyChecking=no "stable-je@stable-je-cb.count.ly" \ + "sudo su -c 'bash /opt/deploy.sh > /var/log/github-action/$LOG_FILE 2>&1 &'" + fi +fi + diff --git a/bin/scripts/member-managament/delete_old_members.js b/bin/scripts/member-managament/delete_old_members.js index a52639e79f9..13f88681426 100644 --- a/bin/scripts/member-managament/delete_old_members.js +++ b/bin/scripts/member-managament/delete_old_members.js @@ -17,7 +17,8 @@ var days = 30; //query states not logged in in last N days , but logged in at least once var ts = Math.round(Date.now() / 1000) - days * 24 * 60 * 60; -var query = {"$and": [{"last_login": {"$lt": ts}}, {"last_login": {"$exists": true}}]}; +var query = {"$and": [{"last_login": {"$lt": ts}}, {"last_login": {"$exists": true}}, {"global_admin": {"$ne": true}}]}; +// {"global_admin": {"$ne": true}} ensures that global admins are excluded from deletion. //although mogodb does not return null on $lt, keep like above for safety @@ -120,4 +121,4 @@ function sendRequest(params, callback) { console.log(e); callback({"err": 'Failed to send'}); } -} \ No newline at end of file +} diff --git a/bin/scripts/member-managament/disable_2fa.js b/bin/scripts/member-managament/disable_2fa.js new file mode 100644 index 00000000000..61eb794816b --- /dev/null +++ b/bin/scripts/member-managament/disable_2fa.js @@ -0,0 +1,54 @@ +/* +Script should be placed in ./bin/scripts/member-managament/disable_2fa.js + +Script is used to disable user(s) 2FA by email. +*/ + +var pluginManager = require('../../../plugins/pluginManager.js'); + +const dry_run = false; //if set true, there will be only information outputted about users in the email list, but 2FA disable operation will not be triggered. +//const EMAILS = ["test@mail.com", "test2@mail.com"]; +const EMAILS = ['']; + +if (dry_run) { + console.log("This is a dry run"); + console.log("Members will only be listed, 2FA will not be disabled"); +} + +pluginManager.dbConnection().then(async(countlyDb) => { + try { + // Find the users by email + let users = []; + users = await getUsers(countlyDb, EMAILS); + + console.log(`The following ${users.length} user(s) 2FA will be disabled: `); + console.log(JSON.stringify(users)); + if (!dry_run) { + await countlyDb.collection('members').updateMany({_id: {$in: users.map(user=>user._id)}}, + { + $set: {"two_factor_auth.enabled": false}, + $unset: {"two_factor_auth.secret_token": ""} + }); + console.log("All done"); + } + } + catch (error) { + console.log("ERROR: "); + console.log(error); + } + finally { + countlyDb.close(); + } +}); + +function getUsers(db, emails) { + const query = {}; + if (emails?.length) { + query.email = { + $in: emails + }; + } + return db.collection('members').find(query, { + projection: { _id: 1, email: 1 } + }).toArray(); +} \ No newline at end of file diff --git a/bin/upgrade/24.12/upgrade.mongo.80.sh b/bin/upgrade/24.12/upgrade.mongo.80.sh index 46fea7095d3..193f67dc9ca 100644 --- a/bin/upgrade/24.12/upgrade.mongo.80.sh +++ b/bin/upgrade/24.12/upgrade.mongo.80.sh @@ -126,6 +126,7 @@ mongosh --nodb --eval 'var conn; print("Waiting for MongoDB connection on port 2 if [ "$isAuth" -eq "1" ]; then echo "run this command with authentication to upgrade to 8.0" + # shellcheck disable=SC2028 echo "mongosh admin --eval \"db.adminCommand( { setFeatureCompatibilityVersion: \\\8.0\\\", confirm: true } )\"" elif ! mongosh admin --eval "printjson(db.adminCommand( { getParameter: 1, featureCompatibilityVersion: 1 } ))" ; then echo "Could not connect to MongodB, run this command when Mongo is up and running" diff --git a/bin/upgrade/DEV/add_creation_date_for_existing_alerts.js b/bin/upgrade/DEV/add_creation_date_for_existing_alerts.js new file mode 100644 index 00000000000..7ad693b0331 --- /dev/null +++ b/bin/upgrade/DEV/add_creation_date_for_existing_alerts.js @@ -0,0 +1,25 @@ +// Script that adds creation date for existing alerts. + +const pluginManager = require('../../../plugins/pluginManager.js'); + +pluginManager.dbConnection().then(async(countlyDb) => { + try { + await countlyDb.collection('alerts').updateMany( + { createdAt: { $exists: false } }, + [ + { + $set: { + createdAt: { $toDouble: { $toDate: "$_id" } } + } + } + ] + ); + console.log("Finished adding creation date for existing alerts."); + } + catch (error) { + console.log(`Error adding creation date for existing alerts: ${error}`); + } + finally { + countlyDb.close(); + } +}); \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 40523cb6c1a..62e26ea1a0c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,10 +16,10 @@ services: countly-api: image: 'countly/api:latest' - # Countly Enterprise: image: 'gcr.io/countly-01/api:20.11.2' + # Countly Enterprise: image: 'gcr.io/countly-01/api:24.12' environment: - COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,logger,systemlogs,populator,reports,crashes,push,star-rating,slipping-away-users,compare,server-stats,dbviewer,times-of-day,compliance-hub,alerts,onboarding,consolidate,remote-config,hooks,dashboards,sdk,data-manager - # Countly Enterprise: - COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,license,drill,funnels,retention_segments,flows,cohorts,surveys,remote-config,ab-testing,formulas,activity-map,concurrent_users,revenue,logger,systemlogs,populator,reports,crashes,push,geo,block,restrict,users,star-rating,slipping-away-users,compare,server-stats,assistant,dbviewer,crash_symbolication,crashes-jira,groups,white-labeling,alerts,times-of-day,compliance-hub,onboarding,active_users,performance-monitoring,config-transfer,consolidate,data-manager,hooks,dashboards,sdk + # Countly Enterprise: - COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,license,drill,funnels,retention_segments,flows,cohorts,surveys,remote-config,ab-testing,formulas,activity-map,concurrent_users,revenue,logger,systemlogs,populator,reports,crashes,push,geo,block,users,star-rating,slipping-away-users,compare,server-stats,dbviewer,crash_symbolication,crashes-jira,groups,white-labeling,alerts,times-of-day,compliance-hub,onboarding,active_users,performance-monitoring,config-transfer,consolidate,data-manager,hooks,dashboards,sdk - COUNTLY_CONFIG__MONGODB_HOST=mongodb - COUNTLY_CONFIG_API_API_WORKERS=4 # CPU core count - COUNTLY_CONFIG__FILESTORAGE="gridfs" @@ -37,10 +37,10 @@ services: countly-frontend: image: 'countly/frontend:latest' - # Countly Enterprise: image: 'gcr.io/countly-01/frontend:20.11.2' + # Countly Enterprise: image: 'gcr.io/countly-01/frontend:24.12' environment: - COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,logger,systemlogs,populator,reports,crashes,push,star-rating,slipping-away-users,compare,server-stats,dbviewer,times-of-day,compliance-hub,alerts,onboarding,consolidate,remote-config,hooks,dashboards,sdk,data-manager - # Countly Enterprise: - COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,license,drill,funnels,retention_segments,flows,cohorts,surveys,remote-config,ab-testing,formulas,activity-map,concurrent_users,revenue,logger,systemlogs,populator,reports,crashes,push,geo,block,restrict,users,star-rating,slipping-away-users,compare,server-stats,assistant,dbviewer,crash_symbolication,crashes-jira,groups,white-labeling,alerts,times-of-day,compliance-hub,onboarding,active_users,performance-monitoring,config-transfer,consolidate,data-manager,hooks,dashboards,sdk + # Countly Enterprise: - COUNTLY_PLUGINS=mobile,web,desktop,plugins,density,locale,browser,sources,views,license,drill,funnels,retention_segments,flows,cohorts,surveys,remote-config,ab-testing,formulas,activity-map,concurrent_users,revenue,logger,systemlogs,populator,reports,crashes,push,geo,block,users,star-rating,slipping-away-users,compare,server-stats,dbviewer,crash_symbolication,crashes-jira,groups,white-labeling,alerts,times-of-day,compliance-hub,onboarding,active_users,performance-monitoring,config-transfer,consolidate,data-manager,hooks,dashboards,sdk - COUNTLY_CONFIG__MONGODB_HOST=mongodb - NODE_OPTIONS="--max-old-space-size=2048" networks: diff --git a/frontend/express/app.js b/frontend/express/app.js index 7c8e8341988..77eaa3d0fed 100644 --- a/frontend/express/app.js +++ b/frontend/express/app.js @@ -6,6 +6,22 @@ // Set process name process.title = "countly: dashboard node " + process.argv[1]; +var fs = require('fs'); +var path = require('path'); +var IS_FLEX = false; + +if (fs.existsSync(path.resolve('/opt/deployment_env.json'))) { + var deploymentConf = fs.readFileSync('/opt/deployment_env.json', 'utf8'); + try { + if (JSON.parse(deploymentConf).DEPLOYMENT_ID) { + IS_FLEX = true; + } + } + catch (e) { + IS_FLEX = false; + } +} + var versionInfo = require('./version.info'), pack = require('../../package.json'), COUNTLY_VERSION = versionInfo.version, @@ -27,8 +43,6 @@ var versionInfo = require('./version.info'), } }), crypto = require('crypto'), - fs = require('fs'), - path = require('path'), jimp = require('jimp'), flash = require('connect-flash'), cookieParser = require('cookie-parser'), @@ -66,7 +80,13 @@ var COUNTLY_NAMED_TYPE = "Countly Lite v" + COUNTLY_VERSION; var COUNTLY_TYPE_CE = true; var COUNTLY_TRIAL = (versionInfo.trial) ? true : false; var COUNTLY_TRACK_TYPE = "OSS"; -if (versionInfo.footer) { + +if (IS_FLEX) { + COUNTLY_NAMED_TYPE = "Countly v" + COUNTLY_VERSION; + COUNTLY_TYPE_CE = false; + COUNTLY_TRACK_TYPE = "Flex"; +} +else if (versionInfo.footer) { COUNTLY_NAMED_TYPE = versionInfo.footer; COUNTLY_TYPE_CE = false; if (COUNTLY_NAMED_TYPE === "Countly Cloud") { @@ -116,8 +136,8 @@ plugins.setConfigs("frontend", { session_timeout: 30, use_google: true, code: true, - google_maps_api_key: "", offline_mode: false, + self_tracking: "", }); if (!plugins.isPluginEnabled('tracker')) { @@ -137,7 +157,6 @@ plugins.setUserConfigs("frontend", { session_timeout: false, use_google: false, code: false, - google_maps_api_key: "" }); plugins.setConfigs("security", { @@ -151,8 +170,8 @@ plugins.setConfigs("security", { password_rotation: 3, password_autocomplete: true, robotstxt: "User-agent: *\nDisallow: /", - dashboard_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nStrict-Transport-Security:max-age=31536000 ; includeSubDomains\nX-Content-Type-Options: nosniff", - api_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nAccess-Control-Allow-Origin:*", + dashboard_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nStrict-Transport-Security:max-age=31536000; includeSubDomains; preload\nX-Content-Type-Options: nosniff", + api_additional_headers: "X-Frame-Options:deny\nX-XSS-Protection:1; mode=block\nStrict-Transport-Security:max-age=31536000; includeSubDomains; preload\nAccess-Control-Allow-Origin:*", dashboard_rate_limit_window: 60, dashboard_rate_limit_requests: 500 }); @@ -907,8 +926,9 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ countly_tracking = plugins.isPluginEnabled('tracker') ? true : plugins.getConfig('frontend').countly_tracking, countly_domain = plugins.getConfig('api').domain, licenseNotification, licenseError; + var isLocked = false; configs.export_limit = plugins.getConfig("api").export_limit; - app.loadThemeFiles(configs.theme, function(theme) { + app.loadThemeFiles(configs.theme, async function(theme) { if (configs._user.theme) { res.cookie("theme", configs.theme); } @@ -922,6 +942,13 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ if (member.upgrade) { countlyDb.collection('members').update({"_id": member._id}, {$unset: {upgrade: ""}}, function() {}); } + if (IS_FLEX) { + let locked = await countlyDb.collection('mycountly').findOne({_id: 'lockServer'}); + if (locked?.isLocked === true) { + isLocked = true; + } + + } if (req.session.licenseError) { licenseError = req.session.licenseError; @@ -989,6 +1016,8 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ helpCenterLink: COUNTLY_HELPCENTER_LINK, featureRequestLink: COUNTLY_FEATUREREQUEST_LINK, }, + mycountly: IS_FLEX, + isLocked: isLocked, }; diff --git a/frontend/express/public/.well-known/security.txt b/frontend/express/public/.well-known/security.txt new file mode 100644 index 00000000000..1db00744410 --- /dev/null +++ b/frontend/express/public/.well-known/security.txt @@ -0,0 +1,7 @@ +# If you would like to report a security issue with Countly Server, Countly SDKs +# please get in touch via the below method +Contact: mailto:security@count.ly +Expires: 2025-03-14T00:00:00.000Z +Preferred-Languages: en +Canonical: https://securitytxt.org/.well-known/security.txt +Policy: https://countly.com/legal/privacy-policy \ No newline at end of file diff --git a/frontend/express/public/core/app-management/stylesheets/_main.scss b/frontend/express/public/core/app-management/stylesheets/_main.scss index b3261f3bd02..21c4ae006b5 100644 --- a/frontend/express/public/core/app-management/stylesheets/_main.scss +++ b/frontend/express/public/core/app-management/stylesheets/_main.scss @@ -85,7 +85,7 @@ } } &__colorpicker{ - .picker-body {overflow: auto;} + .cly-vue-color-picker__picker {overflow: auto;} } &__button{ &:active, &:focus, &:hover {background-color: unset !important;border-color: unset !important;} @@ -193,4 +193,4 @@ width: 177%; } } -} \ No newline at end of file +} diff --git a/frontend/express/public/core/date-presets/javascripts/countly.views.js b/frontend/express/public/core/date-presets/javascripts/countly.views.js old mode 100755 new mode 100644 index a0830b9e534..03478c00e13 --- a/frontend/express/public/core/date-presets/javascripts/countly.views.js +++ b/frontend/express/public/core/date-presets/javascripts/countly.views.js @@ -1,4 +1,4 @@ -/*global Vue app, countlyVue, CV, countlyGlobal, groupsModel, _, CountlyHelpers, countlyPresets*/ +/*global Vue app, countlyVue, CV, countlyGlobal, groupsModel, _, CountlyHelpers, countlyPresets, countlyAuth*/ (function() { @@ -459,11 +459,13 @@ }); }; - app.route("/manage/date-presets", "date-presets", function() { - const PresetManagementView = getManagementView(); - this.renderWhenReady(PresetManagementView); - }); + if (countlyAuth.validateCreate('core')) { + app.route("/manage/date-presets", "date-presets", function() { + const PresetManagementView = getManagementView(); + this.renderWhenReady(PresetManagementView); + }); - app.addMenu("management", {code: "presets", permission: "core", url: "#/manage/date-presets", text: "sidebar.management.presets", priority: 30}); + app.addMenu("management", {code: "presets", permission: "core", url: "#/manage/date-presets", text: "sidebar.management.presets", priority: 30}); + } })(); \ No newline at end of file diff --git a/frontend/express/public/core/events/javascripts/countly.details.models.js b/frontend/express/public/core/events/javascripts/countly.details.models.js index 913f7f34c62..04a5fcbcf8b 100644 --- a/frontend/express/public/core/events/javascripts/countly.details.models.js +++ b/frontend/express/public/core/events/javascripts/countly.details.models.js @@ -895,6 +895,9 @@ return countlyAllEvents.service.fetchAllEventsData(context, period) .then(function(res) { if (res) { + if (Array.isArray(res.list)) { + res.list = res.list.map(eventName => countlyCommon.unescapeHtml(eventName)); + } context.commit("setAllEventsData", res); if ((!context.state.selectedEventName) || (res.map && res.map[context.state.selectedEventName] && !res.map[context.state.selectedEventName].is_visible) || (res.list && res.list.indexOf(context.state.selectedEventName) === -1)) { var appId = countlyCommon.ACTIVE_APP_ID; diff --git a/frontend/express/public/core/onboarding/javascripts/countly.views.js b/frontend/express/public/core/onboarding/javascripts/countly.views.js index 222b069a9d8..e8a147cd5b7 100644 --- a/frontend/express/public/core/onboarding/javascripts/countly.views.js +++ b/frontend/express/public/core/onboarding/javascripts/countly.views.js @@ -37,7 +37,7 @@ types: Object.keys(app.appTypes), appTemplates: appTemplates, populatorProgress: 0, - populatorMaxTime: 60, + populatorMaxTime: 30, isPopulatorFinished: false, isCountlyEE: countlyGlobal.plugins.includes('drill'), selectedAppTemplate: null, diff --git a/frontend/express/public/core/user-management/javascripts/countly.models.js b/frontend/express/public/core/user-management/javascripts/countly.models.js index 147224b3f7b..a87d85aff40 100644 --- a/frontend/express/public/core/user-management/javascripts/countly.models.js +++ b/frontend/express/public/core/user-management/javascripts/countly.models.js @@ -215,5 +215,21 @@ callback(err.responseJSON.result); }); }; + countlyUserManagement.disableTwoFactorAuth = function(id, callback) { + return $.ajax({ + type: "GET", + url: countlyGlobal.path + "/i/two-factor-auth", + data: { + method: "admin_disable", + uid: id + }, + success: function() { + callback(); + }, + error: function(err) { + callback(err.responseJSON.result); + } + }); + }; })((window.countlyUserManagement = window.countlyUserManagement || {})); diff --git a/frontend/express/public/core/user-management/javascripts/countly.views.js b/frontend/express/public/core/user-management/javascripts/countly.views.js index 011c875c7d2..6cb5584eb56 100644 --- a/frontend/express/public/core/user-management/javascripts/countly.views.js +++ b/frontend/express/public/core/user-management/javascripts/countly.views.js @@ -65,6 +65,7 @@ }, roleMap: roleMap, showLogs: countlyGlobal.plugins.indexOf('systemlogs') > -1, + twoFactorAuth: countlyGlobal.plugins.indexOf('two-factor-auth') > -1, tableDynamicCols: tableDynamicCols, userManagementPersistKey: 'userManagement_table_' + countlyCommon.ACTIVE_APP_ID, isGroupPluginEnabled: isGroupPluginEnabled @@ -114,6 +115,9 @@ } }, methods: { + is2faEnabled: function(row) { + return countlyGlobal.member.global_admin && this.twoFactorAuth && row.two_factor_auth && row.two_factor_auth.enabled; + }, handleCommand: function(command, index) { switch (command) { case "delete-user": @@ -157,6 +161,21 @@ }); }); break; + case 'disable-2fa': + countlyUserManagement.disableTwoFactorAuth(index, function(err) { + if (err) { + CountlyHelpers.notify({ + message: CV.i18n('two-factor-auth.faildisable_title'), + type: 'error' + }); + return; + } + CountlyHelpers.notify({ + message: CV.i18n('two-factor-auth.disable_title'), + type: 'success' + }); + }); + break; } }, handleSubmitFilter: function(newFilter) { diff --git a/frontend/express/public/core/user-management/templates/data-table.html b/frontend/express/public/core/user-management/templates/data-table.html index 571376f6630..bf8558ea974 100644 --- a/frontend/express/public/core/user-management/templates/data-table.html +++ b/frontend/express/public/core/user-management/templates/data-table.html @@ -75,6 +75,7 @@

{{i18n('management-users.view-title')}}

sortable="true" prop="username" :label="i18n('management-users.username')"> {{i18n('management-users.view-title')}} sortable="true" prop="email" :label="i18n('management-users.email')"> diff --git a/frontend/express/public/javascripts/countly/countly.event.js b/frontend/express/public/javascripts/countly/countly.event.js index 8307a12fe09..2cf06dbe2aa 100644 --- a/frontend/express/public/javascripts/countly/countly.event.js +++ b/frontend/express/public/javascripts/countly/countly.event.js @@ -89,29 +89,34 @@ })) .then( function() { - return $.when($.ajax({ - type: "GET", - url: countlyCommon.API_PARTS.data.r, - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - "method": "events", - "event": _activeEvent, - "segmentation": currentActiveSegmentation, - "period": _period, - "preventRequestAbort": true - }, - dataType: "json", - success: function(json) { - if (currentActiveEvent === _activeEvent && currentActiveSegmentation === _activeSegmentation) { - _activeLoadedEvent = _activeEvent; - _activeLoadedSegmentation = _activeSegmentation; - _activeEventDb = json; - setMeta(); + if (_activeEvent) { + return $.when($.ajax({ + type: "GET", + url: countlyCommon.API_PARTS.data.r, + data: { + "app_id": countlyCommon.ACTIVE_APP_ID, + "method": "events", + "event": _activeEvent, + "segmentation": currentActiveSegmentation, + "period": _period, + "preventRequestAbort": true + }, + dataType: "json", + success: function(json) { + if (currentActiveEvent === _activeEvent && currentActiveSegmentation === _activeSegmentation) { + _activeLoadedEvent = _activeEvent; + _activeLoadedSegmentation = _activeSegmentation; + _activeEventDb = json; + setMeta(); + } } - } - })).then(function() { + })).then(function() { + return true; + }); + } + else { return true; - }); + } } ); } diff --git a/frontend/express/public/javascripts/countly/countly.template.js b/frontend/express/public/javascripts/countly/countly.template.js old mode 100755 new mode 100644 index aee9ab0fc6e..2873caaf92b --- a/frontend/express/public/javascripts/countly/countly.template.js +++ b/frontend/express/public/javascripts/countly/countly.template.js @@ -1159,7 +1159,7 @@ var AppRouter = Backbone.Router.extend({ } }); - if (countlyAuth.validateRead('core')) { + if (countlyAuth.validateCreate('core')) { self.addSubMenu("management", {code: "longtasks", permission: "core", url: "#/manage/tasks", text: "sidebar.management.longtasks", priority: 10}); } @@ -3901,7 +3901,7 @@ Backbone.history.checkUrl = function() { Backbone.history.noHistory = function(hash) { if (history && history.replaceState) { - history.replaceState(undefined, undefined, hash); + history.replaceState(undefined, undefined, encodeURI(hash)); } else { location.replace(hash); diff --git a/frontend/express/public/javascripts/countly/vue/components/content.js b/frontend/express/public/javascripts/countly/vue/components/content.js index 354db956a34..61301904504 100644 --- a/frontend/express/public/javascripts/countly/vue/components/content.js +++ b/frontend/express/public/javascripts/countly/vue/components/content.js @@ -1,162 +1,205 @@ /* global Vue, CV, countlyCommon */ (function(countlyVue) { Vue.component("cly-content-layout", countlyVue.components.create({ + template: CV.T('/javascripts/countly/vue/templates/content/content.html'), + props: { - popperClass: { - type: String, - required: false, - default: null - }, backgroundColor: { - type: String, - required: false, - default: null + default: null, + type: String + }, + + popperClass: { + default: null, + type: String } }, - data: function() { - return { - currentTab: this.meta?.tabs[0]?.value || null, - isActive: false - }; - }, + computed: { containerClass() { return this.popperClass || 'cly-vue-content-builder__layout-main'; } - }, - template: CV.T('/javascripts/countly/vue/templates/content/content.html'), - methods: { } })); Vue.component("cly-content-header", countlyVue.components.create({ + template: CV.T('/javascripts/countly/vue/templates/content/content-header.html'), + props: { - value: { - type: String, - required: true + backgroundColor: { + default: '#ffffff', + type: String }, - version: { - type: String, - required: false, - default: null + + closeButton: { + default: true, + type: Boolean }, + createdBy: { + default: null, type: String, - required: false, - default: null - }, - toggle: { - type: Boolean, - required: false, - default: false }, - closeButton: { - type: Boolean, - required: false, - default: true + + disableSaveButton: { + default: false, + type: Boolean }, - tabs: { - type: Array, - required: false, - default: function() { - return []; - } + + hideSaveButton: { + default: false, + type: Boolean }, - titleMaxLength: { - type: Number, - required: false, - default: 50 + + isToggleDisabled: { + default: false, + type: Boolean }, - status: { - type: Object, - required: false, - default: function() { - return { show: false, label: 'Status', mode: 'primary' }; - }, + + options: { + default: () => ([]), + type: Array }, + saveButtonLabel: { - type: String, - required: false, - default: CV.i18n('common.save') + default: CV.i18n('common.save'), + type: String }, - disableSaveButton: { - type: Boolean, - required: false, - default: false + + status: { + default: () => ({ + label: 'Status', + mode: 'primary', + show: false + }), + type: Object }, - topDropdownOptions: { - type: Array, - required: false, - default: function() { - return []; - } + + tabs: { + default: () => [], + type: Array }, - hideSaveButton: { - type: Boolean, - required: false, - default: false + + toggle: { + default: false, + type: Boolean }, - backgroundColor: { - type: String, - required: false, - default: '#fff' + + toggleTooltip: { + type: String }, - isToggleActive: { - type: Boolean, - required: false, - default: false + + toggleValue: { + default: false, + type: Boolean }, - isToggleDisabled: { - type: Boolean, - required: false, - default: false + + value: { + required: true, + type: String }, - toggleTooltip: { - type: String, - required: false - } - }, - data: function() { - return { - currentTab: this.tabs[0]?.value || null, - localTitle: countlyCommon.unescapeHtml(this.value), - isEditing: !this.value - }; - }, - watch: { - value: function(newVal) { - this.localTitle = newVal; + + valueMaxLength: { + default: 50, + type: Number }, - currentTab: function(newVal) { - this.$emit('tab-change', newVal); + + version: { + default: null, + type: String } }, - methods: { - toggleChanged(newValue) { - this.$emit('toggleChanged', newValue); + + emits: [ + 'close', + 'handle-command', + 'input', + 'save', + 'switch-toggle', + 'tab-change' + ], + + data: () => ({ + currentTab: null, + + isReadonlyInput: true + }), + + computed: { + activeTab: { + get() { + return this.currentTab || this.tabs[0]?.value; + }, + set(value) { + this.currentTab = value; + this.$emit('tab-change', value); + } }, - close: function() { - this.$emit('close'); + + closeButtonIcon() { + return this.closeButton ? 'cly-io-x' : 'cly-io-arrow-sm-left'; }, - save: function() { - this.$emit('save'); + + dynamicTabsCustomStyle() { + return `background-color: ${this.backgroundColor}`; }, - handleCommand: function(event) { - this.$emit('handle-command', event); + + inputTooltip() { + return this.localValue && this.localValue.length > 30 ? this.localValue : null; }, - handleDoubleClick: function() { - this.isEditing = true; + + isOptionsButtonVisible() { + return !!this.options.length; }, - finishEditing: function() { - if (this.localTitle) { - this.isEditing = false; + + localValue: { + get() { + return countlyCommon.unescapeHtml(this.value); + }, + set(value) { + this.$emit('input', value); } - if (this.localTitle !== this.value) { - this.$emit('input', this.localTitle); + }, + + toggleLocalValue: { + get() { + return this.toggleValue; + }, + set(value) { + this.$emit('switch-toggle', value); } } }, - template: CV.T('/javascripts/countly/vue/templates/content/content-header.html') + + methods: { + onCloseIconClick() { + this.$emit('close'); + }, + + onCommand(event) { + this.$emit('handle-command', event); + }, + + onInputBlur() { + this.toggleInputReadonlyState(); + }, + + onInputContainerClick() { + this.toggleInputReadonlyState(); + }, + + onInputKeydown() { + this.toggleInputReadonlyState(); + }, + + onSaveButtonClick() { + this.$emit('save'); + }, + + toggleInputReadonlyState() { + this.isReadonlyInput = !this.isReadonlyInput; + } + } })); Vue.component("cly-content-body", countlyVue.components.create({ @@ -336,207 +379,234 @@ `, })); - Vue.component("cly-content-step", countlyVue.components.create({ + + // CONSTANTS + + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_COLOR_PICKER = 'color-picker'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_DROPDOWN = 'dropdown'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT = 'input'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER = 'number'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER = 'slider'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER = 'swapper'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH = 'switch'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB = 'tab'; + + const COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE = { + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_COLOR_PICKER]: 'cly-colorpicker', + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_DROPDOWN]: 'el-select', + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT]: 'el-input', + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER]: 'el-input-number', + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER]: 'el-slider', + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER]: 'cly-option-swapper', + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH]: 'el-switch', + [COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_TAB]: 'div' + }; + + const COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_HORIZONTAL = 'horizontal'; + const COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_VERTICAL = 'vertical'; + + Vue.component("cly-content-builder-sidebar-input", countlyVue.components.create({ + template: CV.T('/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html'), + props: { - value: { - type: [String, Number, Boolean, Object], - default: null + componentTooltip: { + default: null, + type: String }, - subHeader: { - type: String, - required: false, - default: null + + disabled: { + default: false, + type: Boolean }, + label: { - type: String, - required: false, - default: null + default: null, + type: String }, - inputType: { - type: String, - required: false, - default: 'text' + + labelIcon: { + default: 'cly-io cly-io-question-mark-circle', + type: String + }, + labelTooltip: { + default: null, + type: String }, + options: { - type: Array, - required: false, - default: () => [] + default: () => [], + type: Array }, + + placement: { + default: COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_HORIZONTAL, + type: String + }, + position: { - type: String, - required: false, - default: 'horizontal' + default: COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_HORIZONTAL, + type: String }, - width: { - type: String, - required: false, - default: null + + subHeader: { + default: null, + type: String }, - inputProps: { - type: Object, - required: false, - default: () => ({}) - } - }, - data() { - return { - localValue: this.initializeLocalValue(), - }; - }, - watch: { + + suffix: { + default: null, + type: String + }, + + type: { + default: COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT, + type: String + }, + value: { - handler: function(newValue) { - this.localValue = this.initializeLocalValue(newValue); - }, - deep: true + default: null, + type: [String, Number, Boolean, Object] }, - localValue: { - handler: function(newValue) { - this.$emit('input', newValue); - }, - deep: true + + size: { + default: null, + type: String + }, + + withComponentTooltip: { + default: false, + type: Boolean + }, + + withLabelTooltip: { + default: false, + type: Boolean } }, - methods: { - initializeLocalValue(val = this.value) { - if (this.inputType === 'switch') { - return val === true; + + emits: [ + 'input' + ], + + computed: { + componentValue: { + get() { + if (this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWITCH) { + return !!this.value; + } + + if (this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT) { + return countlyCommon.unescapeHtml(this.value) || ''; + } + + if (this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER) { + return +this.value || 0; + } + + return this.value || null; + }, + set(newValue) { + this.$emit('input', newValue); } - return val !== undefined ? val : null; - }, - updateValue: function(newValue) { - this.localValue = newValue; - }, - getComponentType: function(type) { - const mapping = { - dropdown: 'el-select', - input: 'el-input', - switch: 'el-switch', - slider: 'el-slider', - 'color-picker': 'cly-colorpicker', - 'input-number': 'el-input-number', - }; - return mapping[type] || 'div'; + }, + + controlsProp() { + return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER ? false : null; + }, + + isDropdownInput() { + return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_DROPDOWN; + }, + + isComponentWithOptions() { + return this.isDropdownInput && Array.isArray(this.options) && this.options.length; + }, + + isLabelTooltipVisible() { + return this.withLabelTooltip && this.labelTooltip; + }, + + isSliderInput() { + return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SLIDER; + }, + + isSuffixVisible() { + return ( + this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_INPUT || + this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_NUMBER + ) && this.suffix; + }, + + isSwapperInput() { + return this.type === COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE_SWAPPER; + }, + + isVerticalInput() { + return this.position === COUNTLY_CONTENT_SIDEBAR_INPUT_PLACEMENT_VERTICAL; + }, + + mainComponent() { + return COUNTLY_CONTENT_SIDEBAR_INPUT_COMPONENT_BY_TYPE[this.type] || 'div'; + }, + + tooltip() { + if (this.withComponentTooltip) { + return this.componentTooltip || null; + } + return null; } - }, - created: function() { - }, - template: ` -
-
{{ subHeader }}
-
-
{{ label }}
- - - - - - -
-
- `, + } })); - Vue.component("cly-option-swapper", countlyVue.components.BaseComponent.extend({ - mixins: [countlyVue.mixins.i18n], + Vue.component("cly-option-swapper", countlyVue.components.create({ + template: CV.T('/javascripts/countly/vue/templates/UI/option-swapper.html'), + props: { - value: { - type: [String, Number], - default: null + disabled: { + default: false, + type: Boolean }, - items: { - type: Array, - default: function() { - return []; - } + + highlightOnSelect: { + default: true, + type: Boolean }, - activeColorCode: { - type: String, - default: '#0166D6' + + options: { + default: () => [], + type: Array }, - width: { - type: String, - default: '100' + + value: { + default: null, + type: [String, Number] } }, - data: function() { - return { - selectedValue: this.items[0].value || 0 - }; - }, - watch: { - value: function(value) { - this.selectedValue = value; + + emits: [ + 'input' + ], + + mixins: [countlyVue.mixins.i18n], + + computed: { + selectedOption: { + get() { + return this.value || this.options[0].value; + }, + set(value) { + this.$emit('input', value); + } } }, + methods: { - numberChange: function(item) { - if (!item.disabled) { - this.selectedValue = item.value; - this.$emit('input', this.selectedValue); + onOptionClick: function(option) { + if (!option.disabled) { + this.selectedOption = option.value; } } - }, - created: function() { - this.selectedValue = this.value || this.items[0].value || 0; - }, - template: ` -
-
-
-
- - - - {{ item.text }} - -
-
-
-
- ` + } })); Vue.component("cly-device-selector", countlyVue.components.BaseComponent.extend({ @@ -602,4 +672,4 @@ ` })); -}(window.countlyVue = window.countlyVue || {})); +}(window.countlyVue = window.countlyVue || {})); \ No newline at end of file diff --git a/frontend/express/public/javascripts/countly/vue/components/date.js b/frontend/express/public/javascripts/countly/vue/components/date.js index bd85afaab8e..6a6ee62bfb0 100644 --- a/frontend/express/public/javascripts/countly/vue/components/date.js +++ b/frontend/express/public/javascripts/countly/vue/components/date.js @@ -1660,9 +1660,48 @@ type: Boolean, default: true, required: false + }, + minDateValue: { + type: Date + }, + isFuture: { + type: Boolean, + default: false + } + }, + computed: { + pickerOptions() { + const defaultRange = { selectableRange: '00:00:00 - 23:59:00' }; + + if (!this.minDateValue) { + return defaultRange; + } + + const now = moment(); + const minDateMoment = moment(this.minDateValue); + const isToday = minDateMoment.isSame(now, 'day'); + + if (this.isFuture && isToday) { + return { + selectableRange: `${now.format('HH:mm:ss')} - 23:59:00` + }; + } + + return defaultRange; } }, - template: '' + template: ` + + ` }); diff --git a/frontend/express/public/javascripts/countly/vue/components/dialog.js b/frontend/express/public/javascripts/countly/vue/components/dialog.js index ec3ac7d59e9..dd97fb47bb1 100644 --- a/frontend/express/public/javascripts/countly/vue/components/dialog.js +++ b/frontend/express/public/javascripts/countly/vue/components/dialog.js @@ -48,8 +48,10 @@ title: {type: String, required: true}, saveButtonLabel: {type: String, required: false, default: CV.i18n("common.save")}, saveButtonVisibility: {type: Boolean, required: false, default: true}, + saveButtonDisabled: {type: Boolean, required: false, default: false}, cancelButtonLabel: {type: String, required: false, default: CV.i18n("common.cancel")}, cancelButtonVisibility: {type: Boolean, required: false, default: true}, + cancelButtonDisabled: {type: Boolean, required: false, default: false}, dialogType: {type: String, required: false, default: "success"}, testId: {type: String, default: 'cly-vue-confirm-dialog-test-id', required: false}, alignCenter: {type: Boolean, default: true} @@ -76,9 +78,12 @@ }, customClass: function() { return this.alignCenter ? "el-dialog--centered" : ""; - } + }, }, methods: { + buttonStyle: function(disabled) { + return disabled ? { opacity: 0.5 } : {}; + }, confirmClicked: function() { this.$emit("confirm"); }, @@ -91,12 +96,16 @@ \ - \ + \ ' })); @@ -261,4 +270,4 @@ countlyVue.mixins.hasFormDialogs = hasFormDialogsMixin; -}(window.countlyVue = window.countlyVue || {})); +}(window.countlyVue = window.countlyVue || {})); \ No newline at end of file diff --git a/frontend/express/public/javascripts/countly/vue/components/form.js b/frontend/express/public/javascripts/countly/vue/components/form.js index 48fce04fce8..c5773e581dd 100644 --- a/frontend/express/public/javascripts/countly/vue/components/form.js +++ b/frontend/express/public/javascripts/countly/vue/components/form.js @@ -172,10 +172,12 @@ }, prevStep: function() { this.setStep(this.currentStepIndex - 1, 'prev'); + this.scrollToTop(); }, nextStep: function() { this.beforeLeavingStep("onlyCurrent"); this.setStep(this.currentStepIndex + 1, 'next', !this.isCurrentStepValid); + this.scrollToTop(); }, reset: function() { var self = this; @@ -233,6 +235,12 @@ } }); } + }, + scrollToTop: function() { + var container = this.$el.getElementsByClassName("cly-vue-drawer__steps-container")[0]; + if (container && container.scrollTop) { + container.scrollTop = 0; + } } } }; diff --git a/frontend/express/public/javascripts/countly/vue/components/helpers.js b/frontend/express/public/javascripts/countly/vue/components/helpers.js index 976ecba4dab..4b61dceedfa 100644 --- a/frontend/express/public/javascripts/countly/vue/components/helpers.js +++ b/frontend/express/public/javascripts/countly/vue/components/helpers.js @@ -1,4 +1,4 @@ -/* global Vue, CV, app, countlyEvent, countlyGlobal, countlyAuth, VueJsonPretty, ElementTiptapPlugin, countlyCommon CountlyHelpers*/ +/* global Vue, CV, $, app, countlyEvent, countlyGlobal, countlyAuth, VueJsonPretty, ElementTiptapPlugin, countlyCommon CountlyHelpers*/ (function(countlyVue) { @@ -580,32 +580,36 @@ Vue.component("cly-event-select", countlyBaseComponent.extend({ mixins: [countlyVue.mixins.i18n], - template: '\ - \ - ', + template: '
\ + \ + \ + \ +

Loading...

\ +
', props: { blacklistedEvents: { type: Array, @@ -626,16 +630,20 @@ singleOptionSettings: { autoPick: true, hideList: true - } + }, + availableEvents: [], + isLoading: false }; }, computed: { hasTitle: function() { return !!this.title; - }, - availableEvents: function() { + } + }, + methods: { + prepareAvailableEvents: function() { var self = this; - var availableEvents = [ + var preparedEventList = [ { "label": this.i18n('sidebar.analytics.sessions'), "name": "[CLY]_session", @@ -648,7 +656,7 @@ } ]; if (countlyGlobal.plugins.indexOf('views') !== -1) { - availableEvents.push({ + preparedEventList.push({ "label": this.i18n('internal-events.[CLY]_view'), "name": "[CLY]_view", "options": [ { label: this.i18n('internal-events.[CLY]_view'), value: '[CLY]_view' } ] @@ -665,7 +673,7 @@ feedbackOptions.push({ label: this.i18n('internal-events.[CLY]_survey'), value: '[CLY]_survey' }); } if (feedbackOptions.length > 0) { - availableEvents.push({ + preparedEventList.push({ "label": this.i18n("sidebar.feedback"), "name": "feedback", "options": feedbackOptions @@ -674,7 +682,7 @@ if (countlyGlobal.plugins.indexOf('compliance-hub') !== -1) { - availableEvents.push({ + preparedEventList.push({ "label": this.i18n('internal-events.[CLY]_consent'), "name": "[CLY]_consent", "options": [ { label: this.i18n('internal-events.[CLY]_consent'), value: '[CLY]_consent' } ] @@ -682,7 +690,7 @@ } if (countlyGlobal.plugins.indexOf('crashes') !== -1) { - availableEvents.push({ + preparedEventList.push({ "label": this.i18n('internal-events.[CLY]_crash'), "name": "[CLY]_crash", "options": [ { label: this.i18n('internal-events.[CLY]_crash'), value: '[CLY]_crash' } ] @@ -697,7 +705,7 @@ { label: this.i18n('internal-events.[CLY]_push_sent'), value: '[CLY]_push_sent' } ] });*/ - availableEvents.push({ + preparedEventList.push({ "label": 'Push Actioned', "name": "[CLY]_push_action", "options": [ @@ -712,26 +720,40 @@ // "noChild": true // } - if (this.selectedApp) { - countlyEvent.getEventsForApps([this.selectedApp], function(eData) { - availableEvents[1].options = eData.map(function(e) { - return {label: countlyCommon.unescapeHtml(e.name), value: e.value}; + return new Promise(function(resolve) { + if (this.selectedApp) { + self.isLoading = true; + countlyEvent.getEventsForApps([this.selectedApp], function(eData) { + preparedEventList[1].options = eData.map(function(e) { + return {label: countlyCommon.unescapeHtml(e.name), value: e.value}; + }); }); - }); - } - else { - availableEvents[1].options = countlyEvent.getEvents().map(function(event) { - return {label: countlyCommon.unescapeHtml(event.name), value: event.key}; - }); - } - - availableEvents = availableEvents.filter(function(evt) { - return !(self.blacklistedEvents.includes(evt.name)); + preparedEventList = preparedEventList.filter(function(evt) { + return !(self.blacklistedEvents.includes(evt.name)); + }); + self.isLoading = false; + resolve(preparedEventList); + } + else { + self.isLoading = true; + $.when(countlyEvent.refreshEvents()).then(function() { + const events = countlyEvent.getEvents(); + preparedEventList[1].options = events.map(function(event) { + return {label: countlyCommon.unescapeHtml(event.name), value: event.key}; + }); + preparedEventList = preparedEventList.filter(function(evt) { + return !(self.blacklistedEvents.includes(evt.name)); + }); + self.isLoading = false; + resolve(preparedEventList); + }); + } }); - - return availableEvents; } }, + created: async function() { + this.availableEvents = await this.prepareAvailableEvents(); + } })); Vue.component("cly-paginate", countlyBaseComponent.extend({ diff --git a/frontend/express/public/javascripts/countly/vue/components/input.js b/frontend/express/public/javascripts/countly/vue/components/input.js index 834381d0a63..97dd5bf5157 100644 --- a/frontend/express/public/javascripts/countly/vue/components/input.js +++ b/frontend/express/public/javascripts/countly/vue/components/input.js @@ -3,90 +3,117 @@ (function(countlyVue) { var _mixins = countlyVue.mixins; - var HEX_COLOR_REGEX = new RegExp('^#([0-9a-f]{3}|[0-9a-f]{6})$', 'i'); + var HEX_COLOR_REGEX = new RegExp('^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$', 'i'); + + Vue.component("cly-colorpicker", countlyVue.components.create({ + template: CV.T('/javascripts/countly/vue/templates/UI/color-picker.html'), + + components: { + picker: window.VueColor.Sketch + }, + + props: { + placement: { + default: 'left', + type: String + }, + + resetValue: { + default: '#FFFFFF', + type: [String, Object] + }, + + testId: { + default: 'cly-colorpicker-test-id', + type: String + }, + + value: { + default: '#FFFFFF', + type: [String, Object], + } + }, + + emits: [ + 'change', + 'input' + ], - Vue.component("cly-colorpicker", countlyVue.components.BaseComponent.extend({ mixins: [ _mixins.i18n ], - props: { - value: {type: [String, Object], default: "#FFFFFF"}, - resetValue: { type: [String, Object], default: "#FFFFFF"}, - placement: {type: String, default: "left"}, - testId: {type: String, default: "cly-colorpicker-test-id"} - }, + data: function() { return { - isOpened: false + isOpened: false, + + previousColor: null }; }, + computed: { - previewStyle: function() { - return { - "background-color": this.value - }; + bodyClasses() { + return ['cly-vue-color-picker__body--' + this.placement]; }, - localValue: { - get: function() { - var rawValue = this.value || this.resetValue; - return rawValue.replace("#", ""); + dropStyles() { + return { color: this.localValue }; + }, + + localValue: { + get() { + return (this.value || this.resetValue); }, - set: function(value) { - var colorValue = "#" + value.replace("#", ""); + set(value) { + let finalValue = value; - if (colorValue.match(HEX_COLOR_REGEX)) { - this.setColor({hex: colorValue}); + if (!finalValue.startsWith('#')) { + finalValue = `#${finalValue}`; + } + + if (finalValue.match(HEX_COLOR_REGEX)) { + this.$emit('input', finalValue); } } - }, - alignment: function() { - return "picker-body--" + this.placement; - }, + } }, + + watch: { + isOpened(value) { + if (value) { + this.previousColor = JSON.parse(JSON.stringify(this.value)); + } + } + }, + methods: { - setColor: function(color) { - var finalColor = color.hex8 || color.hex; + onInputContainerClick() { + this.isOpened = true; + }, - this.$emit("input", finalColor); + close() { + this.isOpened = false; }, - reset: function() { - this.setColor({hex: this.resetValue}); + + onCancelClick() { + this.localValue = this.previousColor; this.close(); }, - open: function() { - this.isOpened = true; + + onConfirmClick() { + this.$emit('change', this.localValue); + this.close(); }, - close: function() { - this.isOpened = false; + + onPickerInput(color) { + this.localValue = color.hex8 || color.hex; }, - confirm: function(color) { - this.$emit('change', color); - this.isOpened = false; + + onResetClick() { + this.localValue = this.resetValue; + this.close(); } - }, - components: { - picker: window.VueColor.Sketch - }, - template: '
\n' + - '
\n' + - '
\n' + - '
\n' + - '\n' + - '
\n' + - '\n' + - '\n' + - '\n' + - '
\n' + - '
\n' + - '\n' + - '
\n' + - '\n' + - '\n' + - '\n' + - '
\n' + - '
\n' + - '
' + } })); Vue.component("cly-dropzone", window.vue2Dropzone); diff --git a/frontend/express/public/javascripts/countly/vue/components/sidebar.js b/frontend/express/public/javascripts/countly/vue/components/sidebar.js index efd541993b3..01f51276600 100644 --- a/frontend/express/public/javascripts/countly/vue/components/sidebar.js +++ b/frontend/express/public/javascripts/countly/vue/components/sidebar.js @@ -898,9 +898,56 @@ return menu; }); }, - handleButtonClick: function() { + getFlexBannerUrl: function() { + let defaultFlexUrl = 'https://flex.countly.com?utm_source=countly_lite_banner'; //fallback + + return new Promise((resolve) => { + try { + if (!window.Countly) { + CountlyHelpers.notify({ + title: "Error", + message: "Countly SDK is not available", + type: "error" + }); + return resolve(defaultFlexUrl); + } + + let CountlySDK; + if (window.Countly.present_feedback_widget) { + CountlySDK = window.Countly; + } + else { + CountlySDK = window.Countly.init({ + app_key: countlyGlobal.frontend_app, + url: countlyGlobal.frontend_server, + device_id: window.Countly.device_id || window.location.hostname, + remote_config: true + }); + } + + if (CountlySDK.fetch_remote_config) { + CountlySDK.fetch_remote_config(function(err, conf) { + if (err) { + resolve(defaultFlexUrl); + } + else { + resolve(conf.flex_banner_url || defaultFlexUrl); + } + }); + } + else { + resolve(defaultFlexUrl); + } + } + catch { + resolve(defaultFlexUrl); + } + }); + }, + handleButtonClick: async function() { + const flexRedirectUrl = await this.getFlexBannerUrl(); CountlyHelpers.goTo({ - url: "https://flex.countly.com", + url: flexRedirectUrl, isExternalLink: true }); } diff --git a/frontend/express/public/javascripts/countly/vue/components/vis.js b/frontend/express/public/javascripts/countly/vue/components/vis.js index 2b516b53f2e..ded07527cd3 100644 --- a/frontend/express/public/javascripts/countly/vue/components/vis.js +++ b/frontend/express/public/javascripts/countly/vue/components/vis.js @@ -462,6 +462,417 @@ } }; + var GraphNotesMixin = { + data: function() { + return { + notes: [], + mergedNotes: [], + }; + }, + computed: { + areNotesHidden: function() { + return this.$store.getters['countlyCommon/getAreNotesHidden']; + } + }, + methods: { + dateChanged: function() { + if (!this.areNotesHidden) { + this.seriesOptions.markPoint.data = []; + var self = this; + setTimeout(() => { + self.getGraphNotes(); + }, 500); + } + }, + getDateFormat: function(date) { + var dateFormats = { + "yyyy-mm-d-hh-mm": "YYYY-MM-D HH:00", + "yyyy-mm-d-h-mm": "YYYY-MM-D H:00", + "d-mmm": "D MMM", + "dd-mmm": "DD MMM", + "d-mmm-yyyy": "D MMM YYYY", + "yyyy-mm-d": "YYYY-MM-D", + "yyyy-m-d": "YYYY-M-D", + "yyyy-mm-dd": "YYYY-MM-DD", + "yyyy-mm": "YYYY-MM", + "yyyy-m": "YYYY-M", + "mmm-yyyy": "MMM YYYY", + "h-00": "H:00", + "hh-00": "HH:00", + "dd/mm/yyyy": "DD/MM/YY", + "mmm": "MMM" + //define other well known formats + }; + + for (var prop in dateFormats) { + if (moment(date, dateFormats[prop], true).isValid()) { + return dateFormats[prop]; + } + } + return null; + }, + graphNotesTimeConverter: function(ts) { + var graphNoteDate = new Date(ts); + if (this.category === "drill" || this.category === "formulas") { + if (this.notationSelectedBucket === "hourly") { + return countlyCommon.formatDate(moment(graphNoteDate), "D MMM YYYY hh:00") || 0; + } + else if (this.notationSelectedBucket === "daily") { + return countlyCommon.formatDate(moment(graphNoteDate), "D MMM YYYY") || 0; + } + else if (this.notationSelectedBucket === "weekly") { + return "W" + moment(graphNoteDate).isoWeek() + " " + moment(graphNoteDate).isoWeekYear(); + } + else if (this.notationSelectedBucket === "monthly") { + return countlyCommon.formatDate(moment(graphNoteDate), "MMM YYYY"); + } + } + else if (this.category === "push-notification") { + if (this.notationSelectedBucket === "weekly") { + return "W" + moment(graphNoteDate).isoWeek(); + } + else if (this.notationSelectedBucket === "monthly") { + return countlyCommon.formatDate(moment(graphNoteDate), "YYYY MMM"); + } + } + else { + var xAxisLabel = null; + if (this.$refs.echarts && this.$refs.echarts.option && this.$refs.echarts.option.xAxis.data) { + xAxisLabel = this.$refs.echarts.option.xAxis.data[0]; + } + else { + return null; + } + var formatType = this.getDateFormat(xAxisLabel); + return countlyCommon.formatDate(moment(ts), formatType) || 0; + } + }, + mergeGraphNotesByDate: function(notes, mergeByWeek) { + var self = this; + const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds + var multiplierCount = 2; + if (this.$refs.echarts && (this.$refs.echarts.getWidth() < 500 && this.$refs.echarts.getWidth() !== 100)) { + multiplierCount = 8; + } + notes.forEach(function(orderedItem) { + orderedItem.dateStr = self.graphNotesTimeConverter(orderedItem.ts); + orderedItem.weekCount = moment(orderedItem.ts).year() - moment(orderedItem.ts).week(); + }); + + if (mergeByWeek) { + for (var k = 1; k < notes.length; k++) { + for (var m = 0; m < k; m++) { + if (notes[k].weekCount === notes[m].weekCount) { + notes[k].dateStr = notes[m].dateStr; + } + } + } + } + + notes.map(function(item) { + item.times = notes.filter(obj => obj.dateStr === item.dateStr).length; + }); + + notes = notes.sort(function(a, b) { + return new Date(b.ts) - new Date(a.ts); + }); + for (var i = 0; i < notes.length - 1; i++) { + if ((i !== notes.length - 1) && (Math.round(Math.abs((notes[i].ts - notes[i + 1].ts) / oneDay)) > 0 && Math.round(Math.abs((notes[i].ts - notes[i + 1].ts) / oneDay)) < multiplierCount)) { + notes[i].hasCloseDate = true; + } + } + return notes; + }, + graphNotesTooltipFormatter: function(arr, params) { + var filteredNotes = arr.filter(x=>x.dateStr === params.data.note.dateStr && x.times > 1); + var minimizeTooltip = false; + var template = ""; + var conditionalClassName = "graph-notes-tooltip"; + + if ((this.$refs && this.$refs.echarts) && (this.$refs.echarts.getHeight() < 200 || this.$refs.echarts.getWidth() < 500)) { + minimizeTooltip = true; + } + + + if (minimizeTooltip) { + conditionalClassName = 'graph-notes-tooltip minimize'; + } + else if (!minimizeTooltip && filteredNotes.length > 0) { + conditionalClassName = 'graph-notes-tooltip bu-mb-4 bu-mx-2'; + } + + if (filteredNotes.length > 0) { + for (var i = 0; i < filteredNotes.length; i++) { + if (i === 0) { + template = '
\ + \ + \ + \ +
\ +
'; + } + template += '
\ +
#' + this.sanitizeHtml(filteredNotes[i].indicator) + '
\ +
\ +
\ +
' + this.sanitizeHtml(filteredNotes[i].owner_name) + '
\ +
' + moment(filteredNotes[i].ts).format("MMM D, YYYY hh:mm A") + '
\ +
\ +
\ + ' + this.sanitizeHtml(filteredNotes[i].noteType) + '\ +
\ +
\ +
' + this.sanitizeHtml(filteredNotes[i].note) + '
\ +
'; + if (i === filteredNotes.length) { + template = "
"; + } + } + } + else { + template += '
\ +
\ +
\ +
' + this.sanitizeHtml(params.data.note.owner_name) + '
\ +
' + moment(params.data.note.ts).format("MMM D, YYYY hh:mm A") + '
\ +
\ +
\ + \ + \ + \ + ' + this.sanitizeHtml(params.data.note.noteType) + '\ +
\ +
\ +
' + this.sanitizeHtml(params.data.note.note) + '
\ +
'; + } + return template; + }, + weekCountToDate: function(year, week, day) { + const firstDayOfYear = new Date(year, 0, 1); + const days = 2 + day + (week - 1) * 7 - firstDayOfYear.getDay(); + return new Date(year, 0, days); + }, + graphNotesFilterChecks: function() { + var returnedObj = {}; + var filter = {}; + var appIds = [countlyCommon.ACTIVE_APP_ID]; + if (this.$parent && this.$parent.data) { + if (this.$parent.data.apps) { + appIds = this.$parent.data.apps; + } + if (this.$parent.data.custom_period && this.$parent.data.custom_period.length) { + if (typeof this.$parent.data.custom_period === "string") { + var convertedTimeObj = countlyCommon.getPeriodObj(this.$parent.data.custom_period); + filter.customPeriod = [convertedTimeObj.start, convertedTimeObj.end]; + } + else if (Array.isArray(this.$parent.data.custom_period)) { + filter.customPeriod = [this.$parent.data.custom_period[0], this.$parent.data.custom_period[1]]; + } + } + } + + if ((this.category === "formulas" || this.category === "drill") && (this.$parent && this.$parent.data)) { + var xAxisLabels = this.option.xAxis.data; + var customPeriodStartDate; + var customPeriodEndDate; + if (this.$parent.data.bucket === "daily") { + customPeriodStartDate = new Date(xAxisLabels[0]).getTime(); + customPeriodEndDate = new Date(xAxisLabels[xAxisLabels.length - 1]).setHours(23, 59); + filter.customPeriod = [customPeriodStartDate, customPeriodEndDate]; + } + else if (this.$parent.data.bucket === "weekly") { + customPeriodStartDate = this.weekCountToDate(xAxisLabels[0].split(' ')[1], xAxisLabels[0].split(' ')[0].split('W')[1], 7); + customPeriodEndDate = this.weekCountToDate(xAxisLabels[xAxisLabels.length - 1].split(' ')[1], xAxisLabels[xAxisLabels.length - 1].split(' ')[0].split('W')[1], 7); + filter.customPeriod = [customPeriodStartDate.getTime(), customPeriodEndDate.getTime()]; + } + else if (this.$parent.data.bucket === "monthly") { + customPeriodStartDate = new Date(xAxisLabels[0]).getTime(); + customPeriodEndDate = new Date(xAxisLabels[xAxisLabels.length - 1]).getTime(); + customPeriodEndDate = moment(customPeriodEndDate).endOf('month')._d.valueOf(); + filter.customPeriod = [customPeriodStartDate, customPeriodEndDate]; + } + } + returnedObj.appIds = appIds; + returnedObj.customPeriod = filter; + return returnedObj; + }, + getGraphNotes: function() { + if (!this.hideNotation && !this.areNotesHidden) { + var self = this; + var chartHeight = 300; + var yAxisHeight = ''; + var filter = {}; + var mergeByDate = false; + + filter = this.graphNotesFilterChecks(); + countlyCommon.getGraphNotes(filter.appIds, filter.customPeriod /*{category: categories.length ? categories : [this.category]}*/).then(function(data) { + self.notes = data.aaData; + }).then(function() { + self.seriesOptions.markPoint.data = []; + if (self.notes && self.notes.length) { + if (self.$refs.echarts) { + chartHeight = self.$refs.echarts.getHeight(); + } + // if custom range date is bigger than 30days, then group notes by week + if ((Array.isArray(countlyCommon.periodObj._period) && countlyCommon.periodObj.currentPeriodArr.length > 30)) { + mergeByDate = true; + } + self.mergedNotes = self.mergeGraphNotesByDate(self.notes, mergeByDate); + self.mergedNotes.forEach(function(note, index) { + if (note.dateStr) { + if (chartHeight < 250 && chartHeight !== 100) { + if (note.hasCloseDate && note.times === 1) { + yAxisHeight = '65%'; + } + else { + yAxisHeight = '60%'; + } + } + else { + if (note.hasCloseDate && note.times === 1) { + yAxisHeight = '80%'; + } + else { + yAxisHeight = '75%'; + } + } + } + + self.seriesOptions.markPoint.data.push({ + note: note, + value: note.times > 1 ? ' ' : note.indicator, + xAxis: note.dateStr, + y: yAxisHeight, + symbolRotate: -20, + symbolSize: note.indicator.length === 1 ? 30 : 40, + }); + + self.seriesOptions.markPoint.data[index].itemStyle = { + color: note.times > 1 ? countlyGraphNotesCommon.COLOR_TAGS[0].label : countlyGraphNotesCommon.COLOR_TAGS.find(x=>x.value === note.color).label + }; + self.seriesOptions.markPoint.emphasis.itemStyle = { + borderColor: "#c5c5c5", + borderWidth: 4 + }; + }); + + self.seriesOptions.markPoint.tooltip = { + transitionDuration: 1, + show: true, + trigger: "item", + confine: true, + extraCssText: 'z-index: 1000', + alwaysShowContent: true, + formatter: function(params) { + return self.graphNotesTooltipFormatter(self.mergedNotes, params); + } + }; + } + }); + } + else { + this.seriesOptions.markPoint.data = []; + } + }, + onClick: function() { + if (!document.querySelectorAll(".graph-overlay").length) { + var overlay = document.createElement("div"); + overlay.setAttribute("class", "graph-overlay"); + overlay.setAttribute("style", "width: 100%; height: 100%; top: 0px; background-color: black; position: absolute; z-index: 999; opacity: 0; display: none;"); + var echarts = document.querySelectorAll('.echarts'); + for (var i = 0; i < echarts.length; i++) { + if (typeof echarts[i] !== 'undefined') { + echarts[i].appendChild(overlay.cloneNode(true)); + } + } + } + if (document.querySelectorAll(".graph-overlay")) { + for (var j = 0; j < document.querySelectorAll(".graph-overlay").length; j++) { + document.querySelectorAll(".graph-overlay")[j].style.display = "block"; + } + } + if (document.querySelectorAll(".graph-notes-tooltip")) { + for (var z = 0; z < document.querySelectorAll(".graph-notes-tooltip").length; z++) { + document.querySelectorAll(".graph-notes-tooltip")[z].parentNode.style.opacity = 1; + } + } + + if (document.querySelectorAll(".graph-tooltip-wrapper")) { + for (var k = 0; k < document.querySelectorAll(".graph-tooltip-wrapper").length; k++) { + document.querySelectorAll(".graph-tooltip-wrapper")[k].parentNode.style.opacity = 1; + } + } + + + if (document.querySelector('x-vue-echarts div .graph-notes-tooltip')) { + localStorage.setItem('showTooltipFlag', true); + document.querySelector('x-vue-echarts div .graph-notes-tooltip').parentNode.addEventListener('mouseleave', window.hideTooltip, true); + } + + if (document.querySelector('x-vue-echarts div .graph-tooltip-wrapper')) { + localStorage.setItem('showTooltipFlag', true); + document.querySelector('x-vue-echarts div .graph-tooltip-wrapper').parentNode.addEventListener('mouseleave', window.hideTooltip, true); + } + countlyCommon.DISABLE_AUTO_REFRESH = true; + } + }, + watch: { + notationSelectedBucket: function() { + this.seriesOptions.markPoint.data = []; + var self = this; + setTimeout(() => { + self.getGraphNotes(); + }, 0); + }, + category: function() { + this.getGraphNotes(); + }, + areNotesHidden: function() { + this.getGraphNotes(); + } + }, + created: function() { + this.getGraphNotes(); + }, + mounted: function() { + window.hideGraphTooltip = function() { + if (typeof document.querySelectorAll(".graph-overlay") !== 'undefined') { + for (var j = 0; j < document.querySelectorAll(".graph-overlay").length; j++) { + document.querySelectorAll(".graph-overlay")[j].style.display = "none"; + } + } + if (typeof document.querySelectorAll(".graph-notes-tooltip") !== 'undefined') { + for (var z = 0; z < document.querySelectorAll(".graph-notes-tooltip").length; z++) { + document.querySelectorAll(".graph-notes-tooltip")[z].parentNode.style.opacity = 0; + } + } + + if (typeof document.querySelectorAll(".graph-tooltip-wrapper") !== 'undefined') { + for (var k = 0; k < document.querySelectorAll(".graph-tooltip-wrapper").length; k++) { + document.querySelectorAll(".graph-tooltip-wrapper")[k].parentNode.style.opacity = 0; + } + } + + + if (document.querySelector('x-vue-echarts div .graph-notes-tooltip')) { + localStorage.removeItem('showTooltipFlag'); + } + + if (document.querySelector('x-vue-echarts div .graph-tooltip-wrapper')) { + localStorage.removeItem('showTooltipFlag'); + } + countlyCommon.DISABLE_AUTO_REFRESH = false; + }; + + window.hideTooltip = function(event) { + if (localStorage.getItem('showTooltipFlag')) { + event.stopImmediatePropagation(); + } + return; + }; + } + }; /* Use xAxis.axisLabel.showMinLabel to change visibility of minimum label @@ -903,7 +1314,8 @@ var BaseLineChart = BaseChart.extend({ mixins: [ - countlyVue.mixins.autoRefresh + countlyVue.mixins.autoRefresh, + GraphNotesMixin ], props: { showToggle: { @@ -936,7 +1348,6 @@ data: function() { return { mixinOptions: {}, - notes: [], seriesOptions: { type: 'line', markPoint: { @@ -956,261 +1367,32 @@ }, animation: false }, - }, - mergedNotes: [], - }; - }, - computed: { - mergedOptions: function() { - var opt = _mergeWith({}, this.baseOptions, this.mixinOptions, this.option, mergeWithCustomizer); - var series = opt.series || []; - for (var i = 0; i < series.length; i++) { - series[i] = _mergeWith({}, this.baseSeriesOptions, this.seriesOptions, series[i]); - } - this.setCalculatedLegendData(opt, series); - - opt.series = series; - - if (this.legendOptions.position !== "bottom") { - opt.grid.right = 0; - } - - if (typeof window.hideGraphTooltip !== "undefined") { - window.hideGraphTooltip(); - } - - return opt; - }, - areNotesHidden: function() { - return this.$store.getters['countlyCommon/getAreNotesHidden']; - } - }, - methods: { - dateChanged: function() { - if (!this.areNotesHidden) { - this.seriesOptions.markPoint.data = []; - var self = this; - setTimeout(() => { - self.getGraphNotes(); - }, 500); - } - }, - getDateFormat: function(date) { - var dateFormats = { - "yyyy-mm-d-hh-mm": "YYYY-MM-D HH:00", - "yyyy-mm-d-h-mm": "YYYY-MM-D H:00", - "d-mmm": "D MMM", - "dd-mmm": "DD MMM", - "d-mmm-yyyy": "D MMM YYYY", - "yyyy-mm-d": "YYYY-MM-D", - "yyyy-m-d": "YYYY-M-D", - "yyyy-mm-dd": "YYYY-MM-DD", - "yyyy-mm": "YYYY-MM", - "yyyy-m": "YYYY-M", - "mmm-yyyy": "MMM YYYY", - "h-00": "H:00", - "hh-00": "HH:00", - "dd/mm/yyyy": "DD/MM/YY", - "mmm": "MMM" - //define other well known formats - }; - - for (var prop in dateFormats) { - if (moment(date, dateFormats[prop], true).isValid()) { - return dateFormats[prop]; - } - } - return null; - }, - graphNotesTimeConverter: function(ts) { - var graphNoteDate = new Date(ts); - if (this.seriesType === "bar") { - return null; - } - else if (this.category === "drill" || this.category === "formulas") { - if (this.notationSelectedBucket === "hourly") { - return countlyCommon.formatDate(moment(graphNoteDate), "D MMM YYYY hh:00") || 0; - } - else if (this.notationSelectedBucket === "daily") { - return countlyCommon.formatDate(moment(graphNoteDate), "D MMM YYYY") || 0; - } - else if (this.notationSelectedBucket === "weekly") { - return "W" + moment(graphNoteDate).isoWeek() + " " + moment(graphNoteDate).isoWeekYear(); - } - else if (this.notationSelectedBucket === "monthly") { - return countlyCommon.formatDate(moment(graphNoteDate), "MMM YYYY"); - } - } - else if (this.category === "push-notification") { - if (this.notationSelectedBucket === "weekly") { - return "W" + moment(graphNoteDate).isoWeek(); - } - else if (this.notationSelectedBucket === "monthly") { - return countlyCommon.formatDate(moment(graphNoteDate), "YYYY MMM"); - } - } - else { - var xAxisLabel = null; - if (this.$refs.echarts && this.$refs.echarts.option && this.$refs.echarts.option.xAxis.data) { - xAxisLabel = this.$refs.echarts.option.xAxis.data[0]; - } - else { - return null; - } - var formatType = this.getDateFormat(xAxisLabel); - return countlyCommon.formatDate(moment(ts), formatType) || 0; - } - }, - mergeGraphNotesByDate: function(notes, mergeByWeek) { - var self = this; - const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds - var multiplierCount = 2; - if (this.$refs.echarts && (this.$refs.echarts.getWidth() < 500 && this.$refs.echarts.getWidth() !== 100)) { - multiplierCount = 8; - } - notes.forEach(function(orderedItem) { - orderedItem.dateStr = self.graphNotesTimeConverter(orderedItem.ts); - orderedItem.weekCount = moment(orderedItem.ts).year() - moment(orderedItem.ts).week(); - }); - - if (mergeByWeek) { - for (var k = 1; k < notes.length; k++) { - for (var m = 0; m < k; m++) { - if (notes[k].weekCount === notes[m].weekCount) { - notes[k].dateStr = notes[m].dateStr; - } - } - } - } - - notes.map(function(item) { - item.times = notes.filter(obj => obj.dateStr === item.dateStr).length; - }); - - notes = notes.sort(function(a, b) { - return new Date(b.ts) - new Date(a.ts); - }); - for (var i = 0; i < notes.length - 1; i++) { - if ((i !== notes.length - 1) && (Math.round(Math.abs((notes[i].ts - notes[i + 1].ts) / oneDay)) > 0 && Math.round(Math.abs((notes[i].ts - notes[i + 1].ts) / oneDay)) < multiplierCount)) { - notes[i].hasCloseDate = true; - } - } - return notes; - }, - graphNotesTooltipFormatter: function(arr, params) { - var filteredNotes = arr.filter(x=>x.dateStr === params.data.note.dateStr && x.times > 1); - var minimizeTooltip = false; - var template = ""; - var conditionalClassName = "graph-notes-tooltip"; - - if ((this.$refs && this.$refs.echarts) && (this.$refs.echarts.getHeight() < 200 || this.$refs.echarts.getWidth() < 500)) { - minimizeTooltip = true; + }, + }; + }, + computed: { + mergedOptions: function() { + var opt = _mergeWith({}, this.baseOptions, this.mixinOptions, this.option, mergeWithCustomizer); + var series = opt.series || []; + for (var i = 0; i < series.length; i++) { + series[i] = _mergeWith({}, this.baseSeriesOptions, this.seriesOptions, series[i]); } + this.setCalculatedLegendData(opt, series); + opt.series = series; - if (minimizeTooltip) { - conditionalClassName = 'graph-notes-tooltip minimize'; - } - else if (!minimizeTooltip && filteredNotes.length > 0) { - conditionalClassName = 'graph-notes-tooltip bu-mb-4 bu-mx-2'; + if (this.legendOptions.position !== "bottom") { + opt.grid.right = 0; } - if (filteredNotes.length > 0) { - for (var i = 0; i < filteredNotes.length; i++) { - if (i === 0) { - template = '
\ - \ - \ - \ -
\ -
'; - } - template += '
\ -
#' + this.sanitizeHtml(filteredNotes[i].indicator) + '
\ -
\ -
\ -
' + this.sanitizeHtml(filteredNotes[i].owner_name) + '
\ -
' + moment(filteredNotes[i].ts).format("MMM D, YYYY hh:mm A") + '
\ -
\ -
\ - ' + this.sanitizeHtml(filteredNotes[i].noteType) + '\ -
\ -
\ -
' + this.sanitizeHtml(filteredNotes[i].note) + '
\ -
'; - if (i === filteredNotes.length) { - template = "
"; - } - } - } - else { - template += '
\ -
\ -
\ -
' + this.sanitizeHtml(params.data.note.owner_name) + '
\ -
' + moment(params.data.note.ts).format("MMM D, YYYY hh:mm A") + '
\ -
\ -
\ - \ - \ - \ - ' + this.sanitizeHtml(params.data.note.noteType) + '\ -
\ -
\ -
' + this.sanitizeHtml(params.data.note.note) + '
\ -
'; - } - return template; - }, - weekCountToDate: function(year, week, day) { - const firstDayOfYear = new Date(year, 0, 1); - const days = 2 + day + (week - 1) * 7 - firstDayOfYear.getDay(); - return new Date(year, 0, days); - }, - graphNotesFilterChecks: function() { - var returnedObj = {}; - var filter = {}; - var appIds = [countlyCommon.ACTIVE_APP_ID]; - if (this.$parent && this.$parent.data) { - if (this.$parent.data.apps) { - appIds = this.$parent.data.apps; - } - if (this.$parent.data.custom_period && this.$parent.data.custom_period.length) { - if (typeof this.$parent.data.custom_period === "string") { - var convertedTimeObj = countlyCommon.getPeriodObj(this.$parent.data.custom_period); - filter.customPeriod = [convertedTimeObj.start, convertedTimeObj.end]; - } - else if (Array.isArray(this.$parent.data.custom_period)) { - filter.customPeriod = [this.$parent.data.custom_period[0], this.$parent.data.custom_period[1]]; - } - } + if (typeof window.hideGraphTooltip !== "undefined") { + window.hideGraphTooltip(); } - if ((this.category === "formulas" || this.category === "drill") && (this.$parent && this.$parent.data)) { - var xAxisLabels = this.option.xAxis.data; - var customPeriodStartDate; - var customPeriodEndDate; - if (this.$parent.data.bucket === "daily") { - customPeriodStartDate = new Date(xAxisLabels[0]).getTime(); - customPeriodEndDate = new Date(xAxisLabels[xAxisLabels.length - 1]).setHours(23, 59); - filter.customPeriod = [customPeriodStartDate, customPeriodEndDate]; - } - else if (this.$parent.data.bucket === "weekly") { - customPeriodStartDate = this.weekCountToDate(xAxisLabels[0].split(' ')[1], xAxisLabels[0].split(' ')[0].split('W')[1], 7); - customPeriodEndDate = this.weekCountToDate(xAxisLabels[xAxisLabels.length - 1].split(' ')[1], xAxisLabels[xAxisLabels.length - 1].split(' ')[0].split('W')[1], 7); - filter.customPeriod = [customPeriodStartDate.getTime(), customPeriodEndDate.getTime()]; - } - else if (this.$parent.data.bucket === "monthly") { - customPeriodStartDate = new Date(xAxisLabels[0]).getTime(); - customPeriodEndDate = new Date(xAxisLabels[xAxisLabels.length - 1]).getTime(); - customPeriodEndDate = moment(customPeriodEndDate).endOf('month')._d.valueOf(); - filter.customPeriod = [customPeriodStartDate, customPeriodEndDate]; - } - } - returnedObj.appIds = appIds; - returnedObj.customPeriod = filter; - return returnedObj; - }, + return opt; + } + }, + methods: { getGraphNotes: function() { if (!this.hideNotation && !this.areNotesHidden) { var self = this; @@ -1295,103 +1477,7 @@ else { this.seriesOptions.markPoint.data = []; } - }, - onClick: function() { - if (!document.querySelectorAll(".graph-overlay").length) { - var overlay = document.createElement("div"); - overlay.setAttribute("class", "graph-overlay"); - overlay.setAttribute("style", "width: 100%; height: 100%; top: 0px; background-color: black; position: absolute; z-index: 999; opacity: 0; display: none;"); - var echarts = document.querySelectorAll('.echarts'); - for (var i = 0; i < echarts.length; i++) { - if (typeof echarts[i] !== 'undefined') { - echarts[i].appendChild(overlay.cloneNode(true)); - } - } - } - if (document.querySelectorAll(".graph-overlay")) { - for (var j = 0; j < document.querySelectorAll(".graph-overlay").length; j++) { - document.querySelectorAll(".graph-overlay")[j].style.display = "block"; - } - } - if (document.querySelectorAll(".graph-notes-tooltip")) { - for (var z = 0; z < document.querySelectorAll(".graph-notes-tooltip").length; z++) { - document.querySelectorAll(".graph-notes-tooltip")[z].parentNode.style.opacity = 1; - } - } - - if (document.querySelectorAll(".graph-tooltip-wrapper")) { - for (var k = 0; k < document.querySelectorAll(".graph-tooltip-wrapper").length; k++) { - document.querySelectorAll(".graph-tooltip-wrapper")[k].parentNode.style.opacity = 1; - } - } - - - if (document.querySelector('x-vue-echarts div .graph-notes-tooltip')) { - localStorage.setItem('showTooltipFlag', true); - document.querySelector('x-vue-echarts div .graph-notes-tooltip').parentNode.addEventListener('mouseleave', window.hideTooltip, true); - } - - if (document.querySelector('x-vue-echarts div .graph-tooltip-wrapper')) { - localStorage.setItem('showTooltipFlag', true); - document.querySelector('x-vue-echarts div .graph-tooltip-wrapper').parentNode.addEventListener('mouseleave', window.hideTooltip, true); - } - countlyCommon.DISABLE_AUTO_REFRESH = true; - } - }, - watch: { - notationSelectedBucket: function() { - this.seriesOptions.markPoint.data = []; - var self = this; - setTimeout(() => { - self.getGraphNotes(); - }, 0); - }, - category: function() { - this.getGraphNotes(); - }, - areNotesHidden: function() { - this.getGraphNotes(); } - }, - created: function() { - this.getGraphNotes(); - }, - mounted: function() { - window.hideGraphTooltip = function() { - if (typeof document.querySelectorAll(".graph-overlay") !== 'undefined') { - for (var j = 0; j < document.querySelectorAll(".graph-overlay").length; j++) { - document.querySelectorAll(".graph-overlay")[j].style.display = "none"; - } - } - if (typeof document.querySelectorAll(".graph-notes-tooltip") !== 'undefined') { - for (var z = 0; z < document.querySelectorAll(".graph-notes-tooltip").length; z++) { - document.querySelectorAll(".graph-notes-tooltip")[z].parentNode.style.opacity = 0; - } - } - - if (typeof document.querySelectorAll(".graph-tooltip-wrapper") !== 'undefined') { - for (var k = 0; k < document.querySelectorAll(".graph-tooltip-wrapper").length; k++) { - document.querySelectorAll(".graph-tooltip-wrapper")[k].parentNode.style.opacity = 0; - } - } - - - if (document.querySelector('x-vue-echarts div .graph-notes-tooltip')) { - localStorage.removeItem('showTooltipFlag'); - } - - if (document.querySelector('x-vue-echarts div .graph-tooltip-wrapper')) { - localStorage.removeItem('showTooltipFlag'); - } - countlyCommon.DISABLE_AUTO_REFRESH = false; - }; - - window.hideTooltip = function(event) { - if (localStorage.getItem('showTooltipFlag')) { - event.stopImmediatePropagation(); - } - return; - }; } }); @@ -1408,12 +1494,33 @@ because the y axis does not adjusts itself when the series changes */ var BaseBarChart = BaseChart.extend({ + mixins: [ + countlyVue.mixins.autoRefresh, + GraphNotesMixin + ], data: function() { return { mixinOptions: {}, seriesOptions: { - type: 'bar' - } + type: 'bar', + markPoint: { + data: [], + label: { + normal: { + show: true, + color: "rgba(255, 251, 251, 1)", + fontWeight: "500", + align: "center", + padding: [1, 1, 1, 2], + }, + }, + emphasis: { + itemStyle: { + } + }, + animation: false + }, + }, }; }, computed: { @@ -1820,7 +1927,7 @@ }, methods: { refreshNotes: function() { - if (this.$refs.echartRef && this.$refs.echartRef.seriesOptions.type === "line") { + if (this.$refs.echartRef) { this.$refs.echartRef.getGraphNotes(); } }, @@ -2687,6 +2794,11 @@ type: String, default: "cly-chart-bar-test-id", required: false + }, + hideNotation: { + type: Boolean, + default: true, + required: false } }, components: { @@ -2703,9 +2815,24 @@ return opt; } }, + methods: { + refresh: function() { + if (!this.areNotesHidden) { + this.getGraphNotes(); + } + }, + notesVisibility: function() { + if (!this.areNotesHidden) { + this.getGraphNotes(); + } + else { + this.seriesOptions.markPoint.data = []; + } + } + }, template: '
\
\ - \ + \ \ @@ -2720,7 +2847,8 @@ :option="chartOptions"\ :autoresize="autoresize"\ @finished="onChartFinished"\ - @datazoom="onDataZoom">\ + @datazoom="onDataZoom"\ + @click="onClick">\ \
\ \ diff --git a/frontend/express/public/javascripts/countly/vue/templates/UI/color-picker.html b/frontend/express/public/javascripts/countly/vue/templates/UI/color-picker.html new file mode 100644 index 00000000000..06a5bece2ff --- /dev/null +++ b/frontend/express/public/javascripts/countly/vue/templates/UI/color-picker.html @@ -0,0 +1,66 @@ +
+
+
+ +
+ +
+ + +
+
+
+ +
+ + + +
+
+
diff --git a/frontend/express/public/javascripts/countly/vue/templates/UI/option-swapper.html b/frontend/express/public/javascripts/countly/vue/templates/UI/option-swapper.html new file mode 100644 index 00000000000..466c856e997 --- /dev/null +++ b/frontend/express/public/javascripts/countly/vue/templates/UI/option-swapper.html @@ -0,0 +1,27 @@ +
+
+ + {{ option.text }} +
+
diff --git a/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html b/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html new file mode 100644 index 00000000000..abbabf0eee9 --- /dev/null +++ b/frontend/express/public/javascripts/countly/vue/templates/content/UI/content-sidebar-input.html @@ -0,0 +1,62 @@ +
+
+ {{ subHeader }} +
+
+ + + + + + +
+ {{ suffix }} +
+
+
+
diff --git a/frontend/express/public/javascripts/countly/vue/templates/content/content-header.html b/frontend/express/public/javascripts/countly/vue/templates/content/content-header.html index 42ce3dfd626..046093d9ddc 100644 --- a/frontend/express/public/javascripts/countly/vue/templates/content/content-header.html +++ b/frontend/express/public/javascripts/countly/vue/templates/content/content-header.html @@ -1,52 +1,98 @@ -
-
-
- +
+
+
+
-
- -
-
- - -
-
-
-

{{ localTitle }}

+ +
+
-
-
-
{{ version }}
-
-
{{ createdBy }}
+
+
+
+ {{ version }} +
+
+ {{ createdBy }} +
-
- -
-
- -
- {{ saveButtonLabel }} -
-
- - - {{ item.label }} - - -
+ +
+ + + {{ saveButtonLabel }} + + + + + {{ option.label }} + + +
-
\ No newline at end of file +
diff --git a/frontend/express/public/javascripts/countly/vue/templates/content/content.html b/frontend/express/public/javascripts/countly/vue/templates/content/content.html index e34f95d3b9b..8abfd6e14d8 100644 --- a/frontend/express/public/javascripts/countly/vue/templates/content/content.html +++ b/frontend/express/public/javascripts/countly/vue/templates/content/content.html @@ -1,9 +1,17 @@ -
-
- - +
+
+
-
- +
+
diff --git a/frontend/express/public/javascripts/countly/vue/templates/datepicker.html b/frontend/express/public/javascripts/countly/vue/templates/datepicker.html index 448b929c7ec..7e7971bb3ed 100644 --- a/frontend/express/public/javascripts/countly/vue/templates/datepicker.html +++ b/frontend/express/public/javascripts/countly/vue/templates/datepicker.html @@ -207,7 +207,7 @@ {{i18n('common.time')}}
- +
diff --git a/frontend/express/public/javascripts/dom/jquery/jquery.js b/frontend/express/public/javascripts/dom/jquery/jquery.js index be4f9d96b11..21896359f9e 100644 --- a/frontend/express/public/javascripts/dom/jquery/jquery.js +++ b/frontend/express/public/javascripts/dom/jquery/jquery.js @@ -1,15 +1,12 @@ /*! - * jQuery JavaScript Library v3.6.0 + * jQuery JavaScript Library v3.7.1 * https://jquery.com/ * - * Includes Sizzle.js - * https://sizzlejs.com/ - * * Copyright OpenJS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * - * Date: 2021-03-02T17:08Z + * Date: 2023-08-28T13:37Z */ ( function( global, factory ) { @@ -23,7 +20,7 @@ // (such as Node.js), expose a factory as module.exports. // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info. + // See ticket trac-14549 for more info. module.exports = global.document ? factory( global, true ) : function( w ) { @@ -150,8 +147,9 @@ function toType( obj ) { -var - version = "3.6.0", +var version = "3.7.1", + + rhtmlSuffix = /HTML$/i, // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -397,6 +395,38 @@ jQuery.extend( { return obj; }, + + // Retrieve the text value of an array of DOM nodes + text: function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += jQuery.text( node ); + } + } + if ( nodeType === 1 || nodeType === 11 ) { + return elem.textContent; + } + if ( nodeType === 9 ) { + return elem.documentElement.textContent; + } + if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; + }, + // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; @@ -419,6 +449,15 @@ jQuery.extend( { return arr == null ? -1 : indexOf.call( arr, elem, i ); }, + isXMLDoc: function( elem ) { + var namespace = elem && elem.namespaceURI, + docElem = elem && ( elem.ownerDocument || elem ).documentElement; + + // Assume HTML when documentElement doesn't yet exist, such as inside + // document fragments. + return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); + }, + // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit merge: function( first, second ) { @@ -520,43 +559,98 @@ function isArrayLike( obj ) { return type === "array" || length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj; } -var Sizzle = -/*! - * Sizzle CSS Selector Engine v2.3.6 - * https://sizzlejs.com/ - * - * Copyright JS Foundation and other contributors - * Released under the MIT license - * https://js.foundation/ - * - * Date: 2021-02-16 - */ -( function( window ) { + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +} +var pop = arr.pop; + + +var sort = arr.sort; + + +var splice = arr.splice; + + +var whitespace = "[\\x20\\t\\r\\n\\f]"; + + +var rtrimCSS = new RegExp( + "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", + "g" +); + + + + +// Note: an element does not contain itself +jQuery.contains = function( a, b ) { + var bup = b && b.parentNode; + + return a === bup || !!( bup && bup.nodeType === 1 && ( + + // Support: IE 9 - 11+ + // IE doesn't have `contains` on SVG. + a.contains ? + a.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); +}; + + + + +// CSS string/identifier serialization +// https://drafts.csswg.org/cssom/#common-serializing-idioms +var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; + +function fcssescape( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; +} + +jQuery.escapeSelector = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + + + + +var preferredDoc = document, + pushNative = push; + +( function() { + var i, - support, Expr, - getText, - isXML, - tokenize, - compile, - select, outermostContext, sortInput, hasDuplicate, + push = pushNative, // Local document vars - setDocument, document, - docElem, + documentElement, documentIsHTML, rbuggyQSA, - rbuggyMatches, matches, - contains, // Instance-specific data - expando = "sizzle" + 1 * new Date(), - preferredDoc = window.document, + expando = jQuery.expando, dirruns = 0, done = 0, classCache = createCache(), @@ -570,47 +664,22 @@ var i, return 0; }, - // Instance methods - hasOwn = ( {} ).hasOwnProperty, - arr = [], - pop = arr.pop, - pushNative = arr.push, - push = arr.push, - slice = arr.slice, - - // Use a stripped-down indexOf as it's faster than native - // https://jsperf.com/thor-indexof-vs-for/5 - indexOf = function( list, elem ) { - var i = 0, - len = list.length; - for ( ; i < len; i++ ) { - if ( list[ i ] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + - "ismap|loop|multiple|open|readonly|required|scoped", + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + + "loop|multiple|open|readonly|required|scoped", // Regular expressions - // http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] - // or strings [capture 3 or capture 4]" + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", @@ -629,101 +698,88 @@ var i, // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + - whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + - "*" ), + rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + + whitespace + "*" ), rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { - "ID": new RegExp( "^#(" + identifier + ")" ), - "CLASS": new RegExp( "^\\.(" + identifier + ")" ), - "TAG": new RegExp( "^(" + identifier + "|[*])" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + ID: new RegExp( "^#(" + identifier + ")" ), + CLASS: new RegExp( "^\\.(" + identifier + ")" ), + TAG: new RegExp( "^(" + identifier + "|[*])" ), + ATTR: new RegExp( "^" + attributes ), + PSEUDO: new RegExp( "^" + pseudos ), + CHILD: new RegExp( + "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + bool: new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + + needsContext: new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, - rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, - rnative = /^[^{]+\{\s*\[native \w/, - // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, // CSS escapes - // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\([^\\r\\n\\f])", "g" ), funescape = function( escape, nonHex ) { var high = "0x" + escape.slice( 1 ) - 0x10000; - return nonHex ? + if ( nonHex ) { // Strip the backslash prefix from a non-hex escape sequence - nonHex : - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // CSS string/identifier serialization - // https://drafts.csswg.org/cssom/#common-serializing-idioms - rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, - fcssescape = function( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + - ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + return nonHex; } - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + return high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, - // Used for iframes - // See setDocument() + // Used for iframes; see `setDocument`. + // Support: IE 9 - 11+, Edge 12 - 18+ // Removing the function wrapper causes a "Permission Denied" - // error in IE + // error in IE/Edge. unloadHandler = function() { setDocument(); }, inDisabledFieldset = addCombinator( function( elem ) { - return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + return elem.disabled === true && nodeName( elem, "fieldset" ); }, { dir: "parentNode", next: "legend" } ); +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + // Optimize for push.apply( _, NodeList ) try { push.apply( @@ -731,32 +787,22 @@ try { preferredDoc.childNodes ); - // Support: Android<4.0 + // Support: Android <=4.0 // Detect silently failing push.apply // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { + push = { + apply: function( target, els ) { pushNative.apply( target, slice.call( els ) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - - // Can't trust NodeList.length - while ( ( target[ j++ ] = els[ i++ ] ) ) {} - target.length = j - 1; + }, + call: function( target ) { + pushNative.apply( target, slice.call( arguments, 1 ) ); } }; } -function Sizzle( selector, context, results, seed ) { +function find( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, @@ -790,11 +836,10 @@ function Sizzle( selector, context, results, seed ) { if ( nodeType === 9 ) { if ( ( elem = context.getElementById( m ) ) ) { - // Support: IE, Opera, Webkit - // TODO: identify versions + // Support: IE 9 only // getElementById can match elements by name instead of ID if ( elem.id === m ) { - results.push( elem ); + push.call( results, elem ); return results; } } else { @@ -804,14 +849,13 @@ function Sizzle( selector, context, results, seed ) { // Element context } else { - // Support: IE, Opera, Webkit - // TODO: identify versions + // Support: IE 9 only // getElementById can match elements by name instead of ID if ( newContext && ( elem = newContext.getElementById( m ) ) && - contains( context, elem ) && + find.contains( context, elem ) && elem.id === m ) { - results.push( elem ); + push.call( results, elem ); return results; } } @@ -822,22 +866,15 @@ function Sizzle( selector, context, results, seed ) { return results; // Class selector - } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && - context.getElementsByClassName ) { - + } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll - if ( support.qsa && - !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - - // Support: IE 8 only - // Exclude object elements - ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + if ( !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { newSelector = selector; newContext = context; @@ -850,7 +887,7 @@ function Sizzle( selector, context, results, seed ) { // as such selectors are not recognized by querySelectorAll. // Thanks to Andrew Dupont for this technique. if ( nodeType === 1 && - ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || @@ -858,11 +895,15 @@ function Sizzle( selector, context, results, seed ) { // We can use :scope instead of the ID hack if the browser // supports it & if we're not changing the context. - if ( newContext !== context || !support.scope ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when + // strict-comparing two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( newContext != context || !support.scope ) { // Capture the context ID, setting it first if necessary if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = nid.replace( rcssescape, fcssescape ); + nid = jQuery.escapeSelector( nid ); } else { context.setAttribute( "id", ( nid = expando ) ); } @@ -895,7 +936,7 @@ function Sizzle( selector, context, results, seed ) { } // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); + return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); } /** @@ -909,7 +950,8 @@ function createCache() { function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + // Use (key + " ") to avoid collision with native prototype properties + // (see https://github.com/jquery/sizzle/issues/157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries @@ -921,7 +963,7 @@ function createCache() { } /** - * Mark a function for special use by Sizzle + * Mark a function for special use by jQuery selector module * @param {Function} fn The function to mark */ function markFunction( fn ) { @@ -952,56 +994,13 @@ function assert( fn ) { } } -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split( "|" ), - i = arr.length; - - while ( i-- ) { - Expr.attrHandle[ arr[ i ] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - a.sourceIndex - b.sourceIndex; - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( ( cur = cur.nextSibling ) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; + return nodeName( elem, "input" ) && elem.type === type; }; } @@ -1011,8 +1010,8 @@ function createInputPseudo( type ) { */ function createButtonPseudo( type ) { return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return ( name === "input" || name === "button" ) && elem.type === type; + return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && + elem.type === type; }; } @@ -1048,14 +1047,13 @@ function createDisabledPseudo( disabled ) { } } - // Support: IE 6 - 11 + // Support: IE 6 - 11+ // Use the isDisabled shortcut property to check for disabled fieldset ancestors return elem.isDisabled === disabled || // Where there is no isDisabled, check manually - /* jshint -W018 */ elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; + inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; @@ -1095,7 +1093,7 @@ function createPositionalPseudo( fn ) { } /** - * Checks a node for validity as a Sizzle context + * Checks a node for validity as a jQuery selector context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ @@ -1103,31 +1101,13 @@ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - var namespace = elem && elem.namespaceURI, - docElem = elem && ( elem.ownerDocument || elem ).documentElement; - - // Support: IE <=8 - // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes - // https://bugs.jquery.com/ticket/4833 - return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); -}; - /** * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document + * @param {Element|Object} [node] An element or document object to use to set the document * @returns {Object} Returns the current document */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, subWindow, +function setDocument( node ) { + var subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected @@ -1141,87 +1121,90 @@ setDocument = Sizzle.setDocument = function( node ) { // Update global variables document = doc; - docElem = document.documentElement; - documentIsHTML = !isXML( document ); + documentElement = document.documentElement; + documentIsHTML = !jQuery.isXMLDoc( document ); + + // Support: iOS 7 only, IE 9 - 11+ + // Older browsers didn't support unprefixed `matches`. + matches = documentElement.matches || + documentElement.webkitMatchesSelector || + documentElement.msMatchesSelector; // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + // Accessing iframe documents after unload throws "permission denied" errors + // (see trac-13936). + // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`, + // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well. + if ( documentElement.msMatchesSelector && - // Support: IE 11, Edge - if ( subWindow.addEventListener ) { - subWindow.addEventListener( "unload", unloadHandler, false ); + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - // Support: IE 9 - 10 only - } else if ( subWindow.attachEvent ) { - subWindow.attachEvent( "onunload", unloadHandler ); - } + // Support: IE 9 - 11+, Edge 12 - 18+ + subWindow.addEventListener( "unload", unloadHandler ); } - // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, - // Safari 4 - 5 only, Opera <=11.6 - 12.x only - // IE/Edge & older browsers don't support the :scope pseudo-class. - // Support: Safari 6.0 only - // Safari 6.0 supports :scope but it's an alias of :root there. - support.scope = assert( function( el ) { - docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); - return typeof el.querySelectorAll !== "undefined" && - !el.querySelectorAll( ":scope fieldset div" ).length; + // Support: IE <10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + documentElement.appendChild( el ).id = jQuery.expando; + return !document.getElementsByName || + !document.getElementsByName( jQuery.expando ).length; } ); - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties - // (excepting IE8 booleans) - support.attributes = assert( function( el ) { - el.className = "i"; - return !el.getAttribute( "className" ); + // Support: IE 9 only + // Check to see if it's possible to do matchesSelector + // on a disconnected node. + support.disconnectedMatch = assert( function( el ) { + return matches.call( el, "*" ); } ); - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert( function( el ) { - el.appendChild( document.createComment( "" ) ); - return !el.getElementsByTagName( "*" ).length; + // Support: IE 9 - 11+, Edge 12 - 18+ + // IE/Edge don't support the :scope pseudo-class. + support.scope = assert( function() { + return document.querySelectorAll( ":scope" ); } ); - // Support: IE<9 - support.getElementsByClassName = rnative.test( document.getElementsByClassName ); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - docElem.appendChild( el ).id = expando; - return !document.getElementsByName || !document.getElementsByName( expando ).length; + // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only + // Make sure the `:has()` argument is parsed unforgivingly. + // We include `*` in the test to detect buggy implementations that are + // _selectively_ forgiving (specifically when the list includes at least + // one valid selector). + // Note that we treat complete lack of support for `:has()` as if it were + // spec-compliant support, which is fine because use of `:has()` in such + // environments will fail in the qSA path and fall back to jQuery traversal + // anyway. + support.cssHas = assert( function() { + try { + document.querySelector( ":has(*,:jqfake)" ); + return false; + } catch ( e ) { + return true; + } } ); // ID filter and find if ( support.getById ) { - Expr.filter[ "ID" ] = function( id ) { + Expr.filter.ID = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute( "id" ) === attrId; }; }; - Expr.find[ "ID" ] = function( id, context ) { + Expr.find.ID = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { - Expr.filter[ "ID" ] = function( id ) { + Expr.filter.ID = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && @@ -1232,7 +1215,7 @@ setDocument = Sizzle.setDocument = function( node ) { // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut - Expr.find[ "ID" ] = function( id, context ) { + Expr.find.ID = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); @@ -1262,40 +1245,18 @@ setDocument = Sizzle.setDocument = function( node ) { } // Tag - Expr.find[ "TAG" ] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else if ( support.qsa ) { - return context.querySelectorAll( tag ); - } - } : - - function( tag, context ) { - var elem, - tmp = [], - i = 0, - - // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } + Expr.find.TAG = function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); - return tmp; - } - return results; - }; + // DocumentFragment nodes don't have gEBTN + } else { + return context.querySelectorAll( tag ); + } + }; // Class - Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + Expr.find.CLASS = function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } @@ -1306,177 +1267,94 @@ setDocument = Sizzle.setDocument = function( node ) { // QSA and matchesSelector support - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; - if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { + var input; - var input; + documentElement.appendChild( el ).innerHTML = + "" + + ""; - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // https://bugs.jquery.com/ticket/12359 - docElem.appendChild( el ).innerHTML = "" + - ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Safari 8+, iOS 8+ - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Firefox <=3.6 - 5 only - // Old Firefox doesn't throw on a badly-escaped identifier. - el.querySelectorAll( "\\\f" ); - rbuggyQSA.push( "[\\r\\n\\f]" ); - } ); - - assert( function( el ) { - el.innerHTML = "" + - ""; - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll( "[name=d]" ).length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } + // Support: iOS <=7 - 8 only + // Boolean attributes and "value" are not treated correctly in some XML documents + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } - // Support: IE9-11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } + // Support: iOS <=7 - 8 only + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } - // Support: Opera 10 - 11 only - // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll( "*,:x" ); - rbuggyQSA.push( ",.*:" ); - } ); - } + // Support: iOS 8 only + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } - if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector ) ) ) ) { + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } - assert( function( el ) { + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE 9 - 11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ + // In some of the document kinds, these selectors wouldn't work natively. + // This is probably OK but for backwards compatibility we want to maintain + // handling them through jQuery traversal in jQuery 3.x. + documentElement.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + } ); - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( el, "*" ); + if ( !support.cssHas ) { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( el, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - } ); + // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ + // Our regular `try-catch` mechanism fails to detect natively-unsupported + // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`) + // in browsers that parse the `:has()` argument as a forgiving selector list. + // https://drafts.csswg.org/selectors/#relational now requires the argument + // to be parsed unforgivingly, but browsers have not yet fully adjusted. + rbuggyQSA.push( ":has" ); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully self-exclusive - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); - } : - function( a, b ) { - if ( b ) { - while ( ( b = b.parentNode ) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { + sortOrder = function( a, b ) { // Flag for duplicate removal if ( a === b ) { @@ -1510,8 +1388,8 @@ setDocument = Sizzle.setDocument = function( node ) { // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq - if ( a == document || a.ownerDocument == preferredDoc && - contains( preferredDoc, a ) ) { + if ( a === document || a.ownerDocument == preferredDoc && + find.contains( preferredDoc, a ) ) { return -1; } @@ -1519,100 +1397,33 @@ setDocument = Sizzle.setDocument = function( node ) { // IE/Edge sometimes throw a "Permission denied" error when strict-comparing // two documents; shallow comparisons work. // eslint-disable-next-line eqeqeq - if ( b == document || b.ownerDocument == preferredDoc && - contains( preferredDoc, b ) ) { + if ( b === document || b.ownerDocument == preferredDoc && + find.contains( preferredDoc, b ) ) { return 1; } // Maintain original order return sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; - } : - function( a, b ) { - - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - return a == document ? -1 : - b == document ? 1 : - /* eslint-enable eqeqeq */ - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( ( cur = cur.parentNode ) ) { - ap.unshift( cur ); - } - cur = b; - while ( ( cur = cur.parentNode ) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[ i ] === bp[ i ] ) { - i++; - } - - return i ? - - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[ i ], bp[ i ] ) : - - // Otherwise nodes in our document sort first - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - /* eslint-disable eqeqeq */ - ap[ i ] == preferredDoc ? -1 : - bp[ i ] == preferredDoc ? 1 : - /* eslint-enable eqeqeq */ - 0; }; return document; -}; +} -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); +find.matches = function( expr, elements ) { + return find( expr, null, null, elements ); }; -Sizzle.matchesSelector = function( elem, expr ) { +find.matchesSelector = function( elem, expr ) { setDocument( elem ); - if ( support.matchesSelector && documentIsHTML && + if ( documentIsHTML && !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); @@ -1620,9 +1431,9 @@ Sizzle.matchesSelector = function( elem, expr ) { // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch ( e ) { @@ -1630,10 +1441,10 @@ Sizzle.matchesSelector = function( elem, expr ) { } } - return Sizzle( expr, document, null, [ elem ] ).length > 0; + return find( expr, document, null, [ elem ] ).length > 0; }; -Sizzle.contains = function( context, elem ) { +find.contains = function( context, elem ) { // Set document vars if needed // Support: IE 11+, Edge 17 - 18+ @@ -1643,10 +1454,11 @@ Sizzle.contains = function( context, elem ) { if ( ( context.ownerDocument || context ) != document ) { setDocument( context ); } - return contains( context, elem ); + return jQuery.contains( context, elem ); }; -Sizzle.attr = function( elem, name ) { + +find.attr = function( elem, name ) { // Set document vars if needed // Support: IE 11+, Edge 17 - 18+ @@ -1659,25 +1471,19 @@ Sizzle.attr = function( elem, name ) { var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) + // Don't get fooled by Object.prototype properties (see trac-13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; -}; + if ( val !== undefined ) { + return val; + } -Sizzle.escape = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); + return elem.getAttribute( name ); }; -Sizzle.error = function( msg ) { +find.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; @@ -1685,76 +1491,44 @@ Sizzle.error = function( msg ) { * Document sorting and removing duplicates * @param {ArrayLike} results */ -Sizzle.uniqueSort = function( results ) { +jQuery.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); + // + // Support: Android <=4.0+ + // Testing for detecting duplicates is unpredictable so instead assume we can't + // depend on duplicate detection in all browsers without a stable sort. + hasDuplicate = !support.sortStable; + sortInput = !support.sortStable && slice.call( results, 0 ); + sort.call( results, sortOrder ); if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); } } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; + while ( j-- ) { + splice.call( results, duplicates[ j ], 1 ); + } } - // Do not include comment or processing instruction nodes + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; - return ret; +jQuery.fn.uniqueSort = function() { + return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); }; -Expr = Sizzle.selectors = { +Expr = jQuery.expr = { // Can be adjusted by the user cacheLength: 50, @@ -1775,12 +1549,12 @@ Expr = Sizzle.selectors = { }, preFilter: { - "ATTR": function( match ) { + ATTR: function( match ) { match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || - match[ 5 ] || "" ).replace( runescape, funescape ); + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) + .replace( runescape, funescape ); if ( match[ 2 ] === "~=" ) { match[ 3 ] = " " + match[ 3 ] + " "; @@ -1789,7 +1563,7 @@ Expr = Sizzle.selectors = { return match.slice( 0, 4 ); }, - "CHILD": function( match ) { + CHILD: function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) @@ -1807,29 +1581,30 @@ Expr = Sizzle.selectors = { // nth-* requires argument if ( !match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); + find.error( match[ 0 ] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[ 4 ] = +( match[ 4 ] ? match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) + ); match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - // other types prohibit arguments + // other types prohibit arguments } else if ( match[ 3 ] ) { - Sizzle.error( match[ 0 ] ); + find.error( match[ 0 ] ); } return match; }, - "PSEUDO": function( match ) { + PSEUDO: function( match ) { var excess, unquoted = !match[ 6 ] && match[ 2 ]; - if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + if ( matchExpr.CHILD.test( match[ 0 ] ) ) { return null; } @@ -1858,36 +1633,36 @@ Expr = Sizzle.selectors = { filter: { - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + TAG: function( nodeNameSelector ) { + var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + return nodeName( elem, expectedNodeName ); }; }, - "CLASS": function( className ) { + CLASS: function( className ) { var pattern = classCache[ className + " " ]; return pattern || - ( pattern = new RegExp( "(^|" + whitespace + - ")" + className + "(" + whitespace + "|$)" ) ) && classCache( - className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); + ( pattern = new RegExp( "(^|" + whitespace + ")" + className + + "(" + whitespace + "|$)" ) ) && + classCache( className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); } ); }, - "ATTR": function( name, operator, check ) { + ATTR: function( name, operator, check ) { return function( elem ) { - var result = Sizzle.attr( elem, name ); + var result = find.attr( elem, name ); if ( result == null ) { return operator === "!="; @@ -1898,22 +1673,34 @@ Expr = Sizzle.selectors = { result += ""; - /* eslint-disable max-len */ - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - /* eslint-enable max-len */ + if ( operator === "=" ) { + return result === check; + } + if ( operator === "!=" ) { + return result !== check; + } + if ( operator === "^=" ) { + return check && result.indexOf( check ) === 0; + } + if ( operator === "*=" ) { + return check && result.indexOf( check ) > -1; + } + if ( operator === "$=" ) { + return check && result.slice( -check.length ) === check; + } + if ( operator === "~=" ) { + return ( " " + result.replace( rwhitespace, " " ) + " " ) + .indexOf( check ) > -1; + } + if ( operator === "|=" ) { + return result === check || result.slice( 0, check.length + 1 ) === check + "-"; + } + return false; }; }, - "CHILD": function( type, what, _argument, first, last ) { + CHILD: function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; @@ -1926,7 +1713,7 @@ Expr = Sizzle.selectors = { } : function( elem, _context, xml ) { - var cache, uniqueCache, outerCache, node, nodeIndex, start, + var cache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), @@ -1941,7 +1728,7 @@ Expr = Sizzle.selectors = { node = elem; while ( ( node = node[ dir ] ) ) { if ( ofType ? - node.nodeName.toLowerCase() === name : + nodeName( node, name ) : node.nodeType === 1 ) { return false; @@ -1960,17 +1747,8 @@ Expr = Sizzle.selectors = { if ( forward && useCache ) { // Seek `elem` from a previously-cached index - - // ...in a gzip-friendly way - node = parent; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; + outerCache = parent[ expando ] || ( parent[ expando ] = {} ); + cache = outerCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; @@ -1982,7 +1760,7 @@ Expr = Sizzle.selectors = { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { - uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } @@ -1991,17 +1769,8 @@ Expr = Sizzle.selectors = { // Use previously-cached element index if available if ( useCache ) { - - // ...in a gzip-friendly way - node = elem; - outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - cache = uniqueCache[ type ] || []; + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + cache = outerCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } @@ -2015,7 +1784,7 @@ Expr = Sizzle.selectors = { ( diff = nodeIndex = 0 ) || start.pop() ) ) { if ( ( ofType ? - node.nodeName.toLowerCase() === name : + nodeName( node, name ) : node.nodeType === 1 ) && ++diff ) { @@ -2023,13 +1792,7 @@ Expr = Sizzle.selectors = { if ( useCache ) { outerCache = node[ expando ] || ( node[ expando ] = {} ); - - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ node.uniqueID ] || - ( outerCache[ node.uniqueID ] = {} ); - - uniqueCache[ type ] = [ dirruns, diff ]; + outerCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { @@ -2047,19 +1810,19 @@ Expr = Sizzle.selectors = { }; }, - "PSEUDO": function( pseudo, argument ) { + PSEUDO: function( pseudo, argument ) { // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes + // https://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); + find.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function - // just as Sizzle does + // just as jQuery does if ( fn[ expando ] ) { return fn( argument ); } @@ -2073,7 +1836,7 @@ Expr = Sizzle.selectors = { matched = fn( seed, argument ), i = matched.length; while ( i-- ) { - idx = indexOf( seed, matched[ i ] ); + idx = indexOf.call( seed, matched[ i ] ); seed[ idx ] = !( matches[ idx ] = matched[ i ] ); } } ) : @@ -2089,14 +1852,14 @@ Expr = Sizzle.selectors = { pseudos: { // Potentially complex pseudos - "not": markFunction( function( selector ) { + not: markFunction( function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); + matcher = compile( selector.replace( rtrimCSS, "$1" ) ); return matcher[ expando ] ? markFunction( function( seed, matches, _context, xml ) { @@ -2115,22 +1878,23 @@ Expr = Sizzle.selectors = { input[ 0 ] = elem; matcher( input, null, xml, results ); - // Don't keep the element (issue #299) + // Don't keep the element + // (see https://github.com/jquery/sizzle/issues/299) input[ 0 ] = null; return !results.pop(); }; } ), - "has": markFunction( function( selector ) { + has: markFunction( function( selector ) { return function( elem ) { - return Sizzle( selector, elem ).length > 0; + return find( selector, elem ).length > 0; }; } ), - "contains": markFunction( function( text ) { + contains: markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { - return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; }; } ), @@ -2140,12 +1904,12 @@ Expr = Sizzle.selectors = { // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { + // https://www.w3.org/TR/selectors/#lang-pseudo + lang: markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test( lang || "" ) ) { - Sizzle.error( "unsupported lang: " + lang ); + find.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { @@ -2164,38 +1928,39 @@ Expr = Sizzle.selectors = { } ), // Miscellaneous - "target": function( elem ) { + target: function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, - "root": function( elem ) { - return elem === docElem; + root: function( elem ) { + return elem === documentElement; }, - "focus": function( elem ) { - return elem === document.activeElement && - ( !document.hasFocus || document.hasFocus() ) && + focus: function( elem ) { + return elem === safeActiveElement() && + document.hasFocus() && !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties - "enabled": createDisabledPseudo( false ), - "disabled": createDisabledPseudo( true ), + enabled: createDisabledPseudo( false ), + disabled: createDisabledPseudo( true ), - "checked": function( elem ) { + checked: function( elem ) { // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return ( nodeName === "input" && !!elem.checked ) || - ( nodeName === "option" && !!elem.selected ); + // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + return ( nodeName( elem, "input" ) && !!elem.checked ) || + ( nodeName( elem, "option" ) && !!elem.selected ); }, - "selected": function( elem ) { + selected: function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly + // Support: IE <=11+ + // Accessing the selectedIndex property + // forces the browser to treat the default option as + // selected when in an optgroup. if ( elem.parentNode ) { // eslint-disable-next-line no-unused-expressions elem.parentNode.selectedIndex; @@ -2205,9 +1970,9 @@ Expr = Sizzle.selectors = { }, // Contents - "empty": function( elem ) { + empty: function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo + // https://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children @@ -2219,49 +1984,49 @@ Expr = Sizzle.selectors = { return true; }, - "parent": function( elem ) { - return !Expr.pseudos[ "empty" ]( elem ); + parent: function( elem ) { + return !Expr.pseudos.empty( elem ); }, // Element/input types - "header": function( elem ) { + header: function( elem ) { return rheader.test( elem.nodeName ); }, - "input": function( elem ) { + input: function( elem ) { return rinputs.test( elem.nodeName ); }, - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; + button: function( elem ) { + return nodeName( elem, "input" ) && elem.type === "button" || + nodeName( elem, "button" ); }, - "text": function( elem ) { + text: function( elem ) { var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && + return nodeName( elem, "input" ) && elem.type === "text" && - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + // Support: IE <10 only + // New HTML5 attribute values (e.g., "search") appear + // with elem.type === "text" ( ( attr = elem.getAttribute( "type" ) ) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection - "first": createPositionalPseudo( function() { + first: createPositionalPseudo( function() { return [ 0 ]; } ), - "last": createPositionalPseudo( function( _matchIndexes, length ) { + last: createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; } ), - "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; } ), - "even": createPositionalPseudo( function( matchIndexes, length ) { + even: createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); @@ -2269,7 +2034,7 @@ Expr = Sizzle.selectors = { return matchIndexes; } ), - "odd": createPositionalPseudo( function( matchIndexes, length ) { + odd: createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); @@ -2277,19 +2042,24 @@ Expr = Sizzle.selectors = { return matchIndexes; } ), - "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? - argument + length : - argument > length ? - length : - argument; + lt: createPositionalPseudo( function( matchIndexes, length, argument ) { + var i; + + if ( argument < 0 ) { + i = argument + length; + } else if ( argument > length ) { + i = length; + } else { + i = argument; + } + for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; } ), - "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + gt: createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); @@ -2299,7 +2069,7 @@ Expr = Sizzle.selectors = { } }; -Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; +Expr.pseudos.nth = Expr.pseudos.eq; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { @@ -2314,7 +2084,7 @@ function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { +function tokenize( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; @@ -2342,13 +2112,13 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { matched = false; // Combinators - if ( ( match = rcombinators.exec( soFar ) ) ) { + if ( ( match = rleadingCombinator.exec( soFar ) ) ) { matched = match.shift(); tokens.push( { value: matched, // Cast descendant combinators to space - type: match[ 0 ].replace( rtrim, " " ) + type: match[ 0 ].replace( rtrimCSS, " " ) } ); soFar = soFar.slice( matched.length ); } @@ -2375,14 +2145,16 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : + if ( parseOnly ) { + return soFar.length; + } - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; + return soFar ? + find.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} function toSelector( tokens ) { var i = 0, @@ -2415,7 +2187,7 @@ function addCombinator( matcher, combinator, base ) { // Check against all ancestor/preceding elements function( elem, context, xml ) { - var oldCache, uniqueCache, outerCache, + var oldCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching @@ -2432,14 +2204,9 @@ function addCombinator( matcher, combinator, base ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - // Support: IE <9 only - // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || - ( outerCache[ elem.uniqueID ] = {} ); - - if ( skip && skip === elem.nodeName.toLowerCase() ) { + if ( skip && nodeName( elem, skip ) ) { elem = elem[ dir ] || elem; - } else if ( ( oldCache = uniqueCache[ key ] ) && + } else if ( ( oldCache = outerCache[ key ] ) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements @@ -2447,7 +2214,7 @@ function addCombinator( matcher, combinator, base ) { } else { // Reuse newcache so results back-propagate to previous elements - uniqueCache[ key ] = newCache; + outerCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { @@ -2479,7 +2246,7 @@ function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { - Sizzle( selector, contexts[ i ], results ); + find( selector, contexts[ i ], results ); } return results; } @@ -2513,38 +2280,37 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS postFinder = setMatcher( postFinder, postSelector ); } return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, + var temp, i, elem, matcherOut, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context - elems = seed || multipleContexts( - selector || "*", - context.nodeType ? [ context ] : context, - [] - ), + elems = seed || + multipleContexts( selector || "*", + context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : - elems, + elems; - matcherOut = matcher ? + if ( matcher ) { - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + // If we have a postFinder, or filtered seed, or non-seed postFilter + // or preexisting results, + matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - // ...intermediate processing is necessary - [] : + // ...intermediate processing is necessary + [] : - // ...otherwise use results directly - results : - matcherIn; + // ...otherwise use results directly + results; - // Find primary matches - if ( matcher ) { + // Find primary matches matcher( matcherIn, matcherOut, context, xml ); + } else { + matcherOut = matcherIn; } // Apply postFilter @@ -2582,7 +2348,7 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS i = matcherOut.length; while ( i-- ) { if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { seed[ temp ] = !( results[ temp ] = elem ); } @@ -2617,15 +2383,21 @@ function matcherFromTokens( tokens ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { - return indexOf( checkContext, elem ) > -1; + return indexOf.call( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { - var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( ( checkContext = context ).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); - // Avoid hanging onto element (issue #299) + // Avoid hanging onto element + // (see https://github.com/jquery/sizzle/issues/299) checkContext = null; return ret; } ]; @@ -2650,11 +2422,10 @@ function matcherFromTokens( tokens ) { i > 1 && elementMatcher( matchers ), i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens - .slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrim, "$1" ), + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens.slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrimCSS, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), @@ -2680,7 +2451,7 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { contextBackup = outermostContext, // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + elems = seed || byElement && Expr.find.TAG( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), @@ -2696,8 +2467,9 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { } // Add elements passing elementMatchers directly to results - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + // Support: iOS <=7 - 9 only + // Tolerate NodeList properties (IE: "length"; Safari: ) matching + // elements by id. (see trac-14142) for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { if ( byElement && elem ) { j = 0; @@ -2712,7 +2484,7 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { } while ( ( matcher = elementMatchers[ j++ ] ) ) { if ( matcher( elem, context || document, xml ) ) { - results.push( elem ); + push.call( results, elem ); break; } } @@ -2775,7 +2547,7 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { - Sizzle.uniqueSort( results ); + jQuery.uniqueSort( results ); } } @@ -2793,7 +2565,7 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { superMatcher; } -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { +function compile( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], @@ -2816,27 +2588,25 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { } // Cache the compiled function - cached = compilerCache( - selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) - ); + cached = compilerCache( selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; -}; +} /** - * A low-level selection function that works with Sizzle's compiled + * A low-level selection function that works with jQuery's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile + * selector function built with jQuery selector compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ -select = Sizzle.select = function( selector, context, results, seed ) { +function select( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( ( selector = compiled.selector || selector ) ); @@ -2850,10 +2620,12 @@ select = Sizzle.select = function( selector, context, results, seed ) { // Reduce context if the leading compound selector is an ID tokens = match[ 0 ] = match[ 0 ].slice( 0 ); if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - context = ( Expr.find[ "ID" ]( token.matches[ 0 ] - .replace( runescape, funescape ), context ) || [] )[ 0 ]; + context = ( Expr.find.ID( + token.matches[ 0 ].replace( runescape, funescape ), + context + ) || [] )[ 0 ]; if ( !context ) { return results; @@ -2866,7 +2638,7 @@ select = Sizzle.select = function( selector, context, results, seed ) { } // Fetch a seed set for right-to-left matching - i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[ i ]; @@ -2879,8 +2651,8 @@ select = Sizzle.select = function( selector, context, results, seed ) { // Search, expanding context for leading sibling combinators if ( ( seed = find( token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || - context + rsibling.test( tokens[ 0 ].type ) && + testContext( context.parentNode ) || context ) ) ) { // If seed is empty or no tokens remain, we can return early @@ -2907,21 +2679,18 @@ select = Sizzle.select = function( selector, context, results, seed ) { !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; -}; +} // One-time assignments +// Support: Android <=4.0 - 4.1+ // Sort stability support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; -// Support: Chrome 14-35+ -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - // Initialize against the default document setDocument(); -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Support: Android <=4.0 - 4.1+ // Detached nodes confoundingly follow *each other* support.sortDetached = assert( function( el ) { @@ -2929,68 +2698,29 @@ support.sortDetached = assert( function( el ) { return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; } ); -// Support: IE<8 -// Prevent attribute/property "interpolation" -// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert( function( el ) { - el.innerHTML = ""; - return el.firstChild.getAttribute( "href" ) === "#"; -} ) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - } ); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert( function( el ) { - el.innerHTML = ""; - el.firstChild.setAttribute( "value", "" ); - return el.firstChild.getAttribute( "value" ) === ""; -} ) ) { - addHandle( "value", function( elem, _name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - } ); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert( function( el ) { - return el.getAttribute( "disabled" ) == null; -} ) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - ( val = elem.getAttributeNode( name ) ) && val.specified ? - val.value : - null; - } - } ); -} - -return Sizzle; - -} )( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; +jQuery.find = find; // Deprecated jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; -jQuery.escapeSelector = Sizzle.escape; +jQuery.unique = jQuery.uniqueSort; + +// These have always been private, but they used to be documented as part of +// Sizzle so let's maintain them for now for backwards compatibility purposes. +find.compile = compile; +find.select = select; +find.setDocument = setDocument; +find.tokenize = tokenize; + +find.escape = jQuery.escapeSelector; +find.getText = jQuery.text; +find.isXML = jQuery.isXMLDoc; +find.selectors = jQuery.expr; +find.support = jQuery.support; +find.uniqueSort = jQuery.uniqueSort; + /* eslint-enable */ +} )(); var dir = function( elem, dir, until ) { @@ -3024,13 +2754,6 @@ var siblings = function( n, elem ) { var rneedsContext = jQuery.expr.match.needsContext; - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -} var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); @@ -3129,8 +2852,8 @@ jQuery.fn.extend( { var rootjQuery, // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) + // Prioritize #id over to avoid XSS via location.hash (trac-9521) + // Strict HTML recognition (trac-11290: must start with <) // Shortcut simple #id case for speed rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, @@ -3281,7 +3004,7 @@ jQuery.fn.extend( { if ( cur.nodeType < 11 && ( targets ? targets.index( cur ) > -1 : - // Don't pass non-elements to Sizzle + // Don't pass non-elements to jQuery#find cur.nodeType === 1 && jQuery.find.matchesSelector( cur, selectors ) ) ) { @@ -3836,7 +3559,7 @@ jQuery.extend( { if ( jQuery.Deferred.exceptionHook ) { jQuery.Deferred.exceptionHook( e, - process.stackTrace ); + process.error ); } // Support: Promises/A+ section 2.3.3.3.4.1 @@ -3864,10 +3587,17 @@ jQuery.extend( { process(); } else { - // Call an optional hook to record the stack, in case of exception + // Call an optional hook to record the error, in case of exception // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getStackHook ) { - process.stackTrace = jQuery.Deferred.getStackHook(); + if ( jQuery.Deferred.getErrorHook ) { + process.error = jQuery.Deferred.getErrorHook(); + + // The deprecated alias of the above. While the name suggests + // returning the stack, not an error instance, jQuery just passes + // it directly to `console.warn` so both will work; an instance + // just better cooperates with source maps. + } else if ( jQuery.Deferred.getStackHook ) { + process.error = jQuery.Deferred.getStackHook(); } window.setTimeout( process ); } @@ -4042,12 +3772,16 @@ jQuery.extend( { // warn about them ASAP rather than swallowing them by default. var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; -jQuery.Deferred.exceptionHook = function( error, stack ) { +// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error +// captured before the async barrier to get the original error cause +// which may otherwise be hidden. +jQuery.Deferred.exceptionHook = function( error, asyncError ) { // Support: IE 8 - 9 only // Console exists when dev tools are open, which can happen at any time if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + window.console.warn( "jQuery.Deferred exception: " + error.message, + error.stack, asyncError ); } }; @@ -4087,7 +3821,7 @@ jQuery.extend( { isReady: false, // A counter to track how many items to wait for before - // the ready event fires. See #6781 + // the ready event fires. See trac-6781 readyWait: 1, // Handle when the DOM is ready @@ -4215,7 +3949,7 @@ function fcamelCase( _all, letter ) { // Convert dashed to camelCase; used by the css and data modules // Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (#9572) +// Microsoft forgot to hump their vendor prefix (trac-9572) function camelCase( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); } @@ -4251,7 +3985,7 @@ Data.prototype = { value = {}; // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. + // but we should not, see trac-8335. // Always return an empty object. if ( acceptData( owner ) ) { @@ -4490,7 +4224,7 @@ jQuery.fn.extend( { while ( i-- ) { // Support: IE 11 only - // The attrs elements can be null (#14894) + // The attrs elements can be null (trac-14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { @@ -4913,9 +4647,9 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); input = document.createElement( "input" ); // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) + // Check state lost if the name is set (trac-11217) // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) + // `name` and `type` must use .setAttribute for WWA (trac-14901) input.setAttribute( "type", "radio" ); input.setAttribute( "checked", "checked" ); input.setAttribute( "name", "t" ); @@ -4939,7 +4673,7 @@ var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); } )(); -// We have to close these tags to support XHTML (#13200) +// We have to close these tags to support XHTML (trac-13200) var wrapMap = { // XHTML parsers do not magically insert elements in the @@ -4965,7 +4699,7 @@ if ( !support.option ) { function getAll( context, tag ) { // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (#15151) + // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) var ret; if ( typeof context.getElementsByTagName !== "undefined" ) { @@ -5048,7 +4782,7 @@ function buildFragment( elems, context, scripts, selection, ignored ) { // Remember the top-level container tmp = fragment.firstChild; - // Ensure the created nodes are orphaned (#12392) + // Ensure the created nodes are orphaned (trac-12392) tmp.textContent = ""; } } @@ -5103,25 +4837,6 @@ function returnFalse() { return false; } -// Support: IE <=9 - 11+ -// focus() and blur() are asynchronous, except when they are no-op. -// So expect focus to be synchronous when the element is already active, -// and blur to be synchronous when the element is not already active. -// (focus and blur are always synchronous in other supported browsers, -// this just defines when we can count on it). -function expectSync( elem, type ) { - return ( elem === safeActiveElement() ) === ( type === "focus" ); -} - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - function on( elem, types, selector, data, fn, one ) { var origFn, type; @@ -5469,15 +5184,15 @@ jQuery.event = { for ( ; cur !== this; cur = cur.parentNode || this ) { - // Don't check non-elements (#13208) - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + // Don't check non-elements (trac-13208) + // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764) if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { matchedHandlers = []; matchedSelectors = {}; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; - // Don't conflict with Object.prototype properties (#13203) + // Don't conflict with Object.prototype properties (trac-13203) sel = handleObj.selector + " "; if ( matchedSelectors[ sel ] === undefined ) { @@ -5559,7 +5274,7 @@ jQuery.event = { el.click && nodeName( el, "input" ) ) { // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", returnTrue ); + leverageNative( el, "click", true ); } // Return false to allow normal processing in the caller @@ -5610,10 +5325,10 @@ jQuery.event = { // synthetic events by interrupting progress until reinvoked in response to // *native* events that it fires directly, ensuring that state changes have // already occurred before other listeners are invoked. -function leverageNative( el, type, expectSync ) { +function leverageNative( el, type, isSetup ) { - // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add - if ( !expectSync ) { + // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add + if ( !isSetup ) { if ( dataPriv.get( el, type ) === undefined ) { jQuery.event.add( el, type, returnTrue ); } @@ -5625,15 +5340,13 @@ function leverageNative( el, type, expectSync ) { jQuery.event.add( el, type, { namespace: false, handler: function( event ) { - var notAsync, result, + var result, saved = dataPriv.get( this, type ); if ( ( event.isTrigger & 1 ) && this[ type ] ) { // Interrupt processing of the outer synthetic .trigger()ed event - // Saved data should be false in such cases, but might be a leftover capture object - // from an async native handler (gh-4350) - if ( !saved.length ) { + if ( !saved ) { // Store arguments for use when handling the inner native event // There will always be at least one argument (an event object), so this array @@ -5642,33 +5355,22 @@ function leverageNative( el, type, expectSync ) { dataPriv.set( this, type, saved ); // Trigger the native event and capture its result - // Support: IE <=9 - 11+ - // focus() and blur() are asynchronous - notAsync = expectSync( this, type ); this[ type ](); result = dataPriv.get( this, type ); - if ( saved !== result || notAsync ) { - dataPriv.set( this, type, false ); - } else { - result = {}; - } + dataPriv.set( this, type, false ); + if ( saved !== result ) { // Cancel the outer synthetic event event.stopImmediatePropagation(); event.preventDefault(); - // Support: Chrome 86+ - // In Chrome, if an element having a focusout handler is blurred by - // clicking outside of it, it invokes the handler synchronously. If - // that handler calls `.remove()` on the element, the data is cleared, - // leaving `result` undefined. We need to guard against this. - return result && result.value; + return result; } // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering the - // native event and prevent that from happening again here. + // (focus or blur), assume that the surrogate already propagated from triggering + // the native event and prevent that from happening again here. // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the // bubbling surrogate propagates *after* the non-bubbling base), but that seems // less bad than duplication. @@ -5678,22 +5380,25 @@ function leverageNative( el, type, expectSync ) { // If this is a native event triggered above, everything is now in order // Fire an inner synthetic event with the original arguments - } else if ( saved.length ) { + } else if ( saved ) { // ...and capture the result - dataPriv.set( this, type, { - value: jQuery.event.trigger( - - // Support: IE <=9 - 11+ - // Extend with the prototype to reset the above stopImmediatePropagation() - jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), - saved.slice( 1 ), - this - ) - } ); - - // Abort handling of the native event - event.stopImmediatePropagation(); + dataPriv.set( this, type, jQuery.event.trigger( + saved[ 0 ], + saved.slice( 1 ), + this + ) ); + + // Abort handling of the native event by all jQuery handlers while allowing + // native handlers on the same element to run. On target, this is achieved + // by stopping immediate propagation just on the jQuery event. However, + // the native event is re-wrapped by a jQuery one on each level of the + // propagation so the only way to stop it for jQuery is to stop it for + // everyone via native `stopPropagation()`. This is not a problem for + // focus/blur which don't bubble, but it does also stop click on checkboxes + // and radios. We accept this limitation. + event.stopPropagation(); + event.isImmediatePropagationStopped = returnTrue; } } } ); @@ -5731,7 +5436,7 @@ jQuery.Event = function( src, props ) { // Create target properties // Support: Safari <=6 - 7 only - // Target should not be a text node (#504, #13143) + // Target should not be a text node (trac-504, trac-13143) this.target = ( src.target && src.target.nodeType === 3 ) ? src.target.parentNode : src.target; @@ -5832,18 +5537,73 @@ jQuery.each( { }, jQuery.event.addProp ); jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + + function focusMappedHandler( nativeEvent ) { + if ( document.documentMode ) { + + // Support: IE 11+ + // Attach a single focusin/focusout handler on the document while someone wants + // focus/blur. This is because the former are synchronous in IE while the latter + // are async. In other browsers, all those handlers are invoked synchronously. + + // `handle` from private data would already wrap the event, but we need + // to change the `type` here. + var handle = dataPriv.get( this, "handle" ), + event = jQuery.event.fix( nativeEvent ); + event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; + event.isSimulated = true; + + // First, handle focusin/focusout + handle( nativeEvent ); + + // ...then, handle focus/blur + // + // focus/blur don't bubble while focusin/focusout do; simulate the former by only + // invoking the handler at the lower level. + if ( event.target === event.currentTarget ) { + + // The setup part calls `leverageNative`, which, in turn, calls + // `jQuery.event.add`, so event handle will already have been set + // by this point. + handle( event ); + } + } else { + + // For non-IE browsers, attach a single capturing handler on the document + // while someone wants focusin/focusout. + jQuery.event.simulate( delegateType, nativeEvent.target, + jQuery.event.fix( nativeEvent ) ); + } + } + jQuery.event.special[ type ] = { // Utilize native event if possible so blur/focus sequence is correct setup: function() { + var attaches; + // Claim the first handler // dataPriv.set( this, "focus", ... ) // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, expectSync ); + leverageNative( this, type, true ); - // Return false to allow normal processing in the caller - return false; + if ( document.documentMode ) { + + // Support: IE 9 - 11+ + // We use the same native handler for focusin & focus (and focusout & blur) + // so we need to coordinate setup & teardown parts between those events. + // Use `delegateType` as the key as `type` is already used by `leverageNative`. + attaches = dataPriv.get( this, delegateType ); + if ( !attaches ) { + this.addEventListener( delegateType, focusMappedHandler ); + } + dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); + } else { + + // Return false to allow normal processing in the caller + return false; + } }, trigger: function() { @@ -5854,14 +5614,84 @@ jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateTyp return true; }, - // Suppress native focus or blur as it's already being fired - // in leverageNative. - _default: function() { - return true; + teardown: function() { + var attaches; + + if ( document.documentMode ) { + attaches = dataPriv.get( this, delegateType ) - 1; + if ( !attaches ) { + this.removeEventListener( delegateType, focusMappedHandler ); + dataPriv.remove( this, delegateType ); + } else { + dataPriv.set( this, delegateType, attaches ); + } + } else { + + // Return false to indicate standard teardown should be applied + return false; + } + }, + + // Suppress native focus or blur if we're currently inside + // a leveraged native-event stack + _default: function( event ) { + return dataPriv.get( event.target, type ); }, delegateType: delegateType }; + + // Support: Firefox <=44 + // Firefox doesn't have focus(in | out) events + // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 + // + // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 + // focus(in | out) events fire after focus & blur events, + // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order + // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 + // + // Support: IE 9 - 11+ + // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch, + // attach a single handler for both events in IE. + jQuery.event.special[ delegateType ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ); + + // Support: IE 9 - 11+ + // We use the same native handler for focusin & focus (and focusout & blur) + // so we need to coordinate setup & teardown parts between those events. + // Use `delegateType` as the key as `type` is already used by `leverageNative`. + if ( !attaches ) { + if ( document.documentMode ) { + this.addEventListener( delegateType, focusMappedHandler ); + } else { + doc.addEventListener( type, focusMappedHandler, true ); + } + } + dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + dataHolder = document.documentMode ? this : doc, + attaches = dataPriv.get( dataHolder, delegateType ) - 1; + + if ( !attaches ) { + if ( document.documentMode ) { + this.removeEventListener( delegateType, focusMappedHandler ); + } else { + doc.removeEventListener( type, focusMappedHandler, true ); + } + dataPriv.remove( dataHolder, delegateType ); + } else { + dataPriv.set( dataHolder, delegateType, attaches ); + } + } + }; } ); // Create mouseenter/leave events using mouseover/out and event-time checks @@ -5956,7 +5786,8 @@ var // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rcleanScript = /^\s*\s*$/g; + + rcleanScript = /^\s*\s*$/g; // Prefer a tbody over its parent table for containing new rows function manipulationTarget( elem, content ) { @@ -6070,7 +5901,7 @@ function domManip( collection, args, callback, ignored ) { // Use the original fragment for the last item // instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). + // being emptied incorrectly in certain situations (trac-8070). for ( ; i < l; i++ ) { node = fragment; @@ -6092,7 +5923,7 @@ function domManip( collection, args, callback, ignored ) { if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; - // Reenable scripts + // Re-enable scripts jQuery.map( scripts, restoreScript ); // Evaluate executable scripts on first document insertion @@ -6111,6 +5942,12 @@ function domManip( collection, args, callback, ignored ) { }, doc ); } } else { + + // Unwrap a CDATA section containing script contents. This shouldn't be + // needed as in XML documents they're already not visible when + // inspecting element contents and in HTML documents they have no + // meaning but we're preserving that logic for backwards compatibility. + // This will be removed completely in 4.0. See gh-4904. DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } @@ -6157,7 +5994,8 @@ jQuery.extend( { if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { - // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + // We eschew jQuery#find here for performance reasons: + // https://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); srcElements = getAll( elem ); @@ -6393,9 +6231,12 @@ jQuery.each( { } ); var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); +var rcustomProp = /^--/; + + var getStyles = function( elem ) { - // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150) // IE throws on elements created in popups // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" var view = elem.ownerDocument.defaultView; @@ -6495,7 +6336,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); } // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (#8908) + // Style of cloned element affects source element cloned (trac-8908) div.style.backgroundClip = "content-box"; div.cloneNode( true ).style.backgroundClip = ""; support.clearCloneStyle = div.style.backgroundClip === "content-box"; @@ -6539,7 +6380,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); trChild = document.createElement( "div" ); table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; - tr.style.cssText = "border:1px solid"; + tr.style.cssText = "box-sizing:content-box;border:1px solid"; // Support: Chrome 86+ // Height set through cssText does not get applied. @@ -6551,7 +6392,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); // In our bodyBackground.html iframe, // display for all div elements is set to "inline", // which causes a problem only in Android 8 Chrome 86. - // Ensuring the div is display: block + // Ensuring the div is `display: block` // gets around this issue. trChild.style.display = "block"; @@ -6575,6 +6416,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); function curCSS( elem, name, computed ) { var width, minWidth, maxWidth, ret, + isCustomProp = rcustomProp.test( name ), // Support: Firefox 51+ // Retrieving style before computed somehow @@ -6585,11 +6427,42 @@ function curCSS( elem, name, computed ) { computed = computed || getStyles( elem ); // getPropertyValue is needed for: - // .css('filter') (IE 9 only, #12537) - // .css('--customProperty) (#3144) + // .css('filter') (IE 9 only, trac-12537) + // .css('--customProperty) (gh-3144) if ( computed ) { + + // Support: IE <=9 - 11+ + // IE only supports `"float"` in `getPropertyValue`; in computed styles + // it's only available as `"cssFloat"`. We no longer modify properties + // sent to `.css()` apart from camelCasing, so we need to check both. + // Normally, this would create difference in behavior: if + // `getPropertyValue` returns an empty string, the value returned + // by `.css()` would be `undefined`. This is usually the case for + // disconnected elements. However, in IE even disconnected elements + // with no styles return `"none"` for `getPropertyValue( "float" )` ret = computed.getPropertyValue( name ) || computed[ name ]; + if ( isCustomProp && ret ) { + + // Support: Firefox 105+, Chrome <=105+ + // Spec requires trimming whitespace for custom properties (gh-4926). + // Firefox only trims leading whitespace. Chrome just collapses + // both leading & trailing whitespace to a single space. + // + // Fall back to `undefined` if empty string returned. + // This collapses a missing definition with property defined + // and set to an empty string but there's no standard API + // allowing us to differentiate them without a performance penalty + // and returning `undefined` aligns with older jQuery. + // + // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED + // as whitespace while CSS does not, but this is not a problem + // because CSS preprocessing replaces them with U+000A LINE FEED + // (which *is* CSS whitespace) + // https://www.w3.org/TR/css-syntax-3/#input-preprocessing + ret = ret.replace( rtrimCSS, "$1" ) || undefined; + } + if ( ret === "" && !isAttached( elem ) ) { ret = jQuery.style( elem, name ); } @@ -6685,7 +6558,6 @@ var // except "table", "table-cell", or "table-caption" // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { letterSpacing: "0", @@ -6707,7 +6579,8 @@ function setPositiveNumber( _elem, value, subtract ) { function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { var i = dimension === "width" ? 1 : 0, extra = 0, - delta = 0; + delta = 0, + marginDelta = 0; // Adjustment may not be necessary if ( box === ( isBorderBox ? "border" : "content" ) ) { @@ -6717,8 +6590,10 @@ function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computed for ( ; i < 4; i += 2 ) { // Both box models exclude margin + // Count margin delta separately to only add it after scroll gutter adjustment. + // This is needed to make negative margins work with `outerHeight( true )` (gh-3982). if ( box === "margin" ) { - delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } // If we get here with a content-box, we're seeking "padding" or "border" or "margin" @@ -6769,7 +6644,7 @@ function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computed ) ) || 0; } - return delta; + return delta + marginDelta; } function getWidthOrHeight( elem, dimension, extra ) { @@ -6867,26 +6742,35 @@ jQuery.extend( { // Don't automatically add "px" to these possibly-unitless properties cssNumber: { - "animationIterationCount": true, - "columnCount": true, - "fillOpacity": true, - "flexGrow": true, - "flexShrink": true, - "fontWeight": true, - "gridArea": true, - "gridColumn": true, - "gridColumnEnd": true, - "gridColumnStart": true, - "gridRow": true, - "gridRowEnd": true, - "gridRowStart": true, - "lineHeight": true, - "opacity": true, - "order": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true + animationIterationCount: true, + aspectRatio: true, + borderImageSlice: true, + columnCount: true, + flexGrow: true, + flexShrink: true, + fontWeight: true, + gridArea: true, + gridColumn: true, + gridColumnEnd: true, + gridColumnStart: true, + gridRow: true, + gridRowEnd: true, + gridRowStart: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + scale: true, + widows: true, + zIndex: true, + zoom: true, + + // SVG-related + fillOpacity: true, + floodOpacity: true, + stopOpacity: true, + strokeMiterlimit: true, + strokeOpacity: true }, // Add in properties whose names you wish to fix before @@ -6921,15 +6805,15 @@ jQuery.extend( { if ( value !== undefined ) { type = typeof value; - // Convert "+=" or "-=" to relative numbers (#7345) + // Convert "+=" or "-=" to relative numbers (trac-7345) if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { value = adjustCSS( elem, name, ret ); - // Fixes bug #9237 + // Fixes bug trac-9237 type = "number"; } - // Make sure that null and NaN values aren't set (#7116) + // Make sure that null and NaN values aren't set (trac-7116) if ( value == null || value !== value ) { return; } @@ -7553,7 +7437,7 @@ function Animation( elem, properties, options ) { remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, @@ -7943,7 +7827,6 @@ jQuery.fx.speeds = { // Based off of the plugin by Clint Helfers, with permission. -// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ jQuery.fn.delay = function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; @@ -8168,8 +8051,7 @@ jQuery.extend( { // Support: IE <=9 - 11 only // elem.tabIndex doesn't always return the // correct value when it hasn't been explicitly set - // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - // Use proper attribute retrieval(#12072) + // Use proper attribute retrieval (trac-12072) var tabindex = jQuery.find.attr( elem, "tabindex" ); if ( tabindex ) { @@ -8273,8 +8155,7 @@ function classesToArray( value ) { jQuery.fn.extend( { addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; + var classNames, cur, curValue, className, i, finalValue; if ( isFunction( value ) ) { return this.each( function( j ) { @@ -8282,36 +8163,35 @@ jQuery.fn.extend( { } ); } - classes = classesToArray( value ); + classNames = classesToArray( value ); - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + if ( cur.indexOf( " " + className + " " ) < 0 ) { + cur += className + " "; } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); + this.setAttribute( "class", finalValue ); } } - } + } ); } return this; }, removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; + var classNames, cur, curValue, className, i, finalValue; if ( isFunction( value ) ) { return this.each( function( j ) { @@ -8323,45 +8203,42 @@ jQuery.fn.extend( { return this.attr( "class", "" ); } - classes = classesToArray( value ); + classNames = classesToArray( value ); - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); + while ( cur.indexOf( " " + className + " " ) > -1 ) { + cur = cur.replace( " " + className + " ", " " ); } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); + this.setAttribute( "class", finalValue ); } } - } + } ); } return this; }, toggleClass: function( value, stateVal ) { - var type = typeof value, + var classNames, className, i, self, + type = typeof value, isValidValue = type === "string" || Array.isArray( value ); - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( @@ -8371,17 +8248,20 @@ jQuery.fn.extend( { } ); } - return this.each( function() { - var className, i, self, classNames; + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + classNames = classesToArray( value ); + + return this.each( function() { if ( isValidValue ) { // Toggle individual class names - i = 0; self = jQuery( this ); - classNames = classesToArray( value ); - while ( ( className = classNames[ i++ ] ) ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; // Check each className given, space separated list if ( self.hasClass( className ) ) { @@ -8515,7 +8395,7 @@ jQuery.extend( { val : // Support: IE <=10 - 11 only - // option.text throws exceptions (#14686, #14858) + // option.text throws exceptions (trac-14686, trac-14858) // Strip and collapse whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace stripAndCollapse( jQuery.text( elem ) ); @@ -8542,7 +8422,7 @@ jQuery.extend( { option = options[ i ]; // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (#2551) + // IE8-9 doesn't update selected after form reset (trac-2551) if ( ( option.selected || i === index ) && // Don't return options that are disabled or in a disabled optgroup @@ -8616,9 +8496,39 @@ jQuery.each( [ "radio", "checkbox" ], function() { // Return jQuery for attributes-only inclusion +var location = window.location; + +var nonce = { guid: Date.now() }; +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml, parserErrorElem; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) {} -support.focusin = "onfocusin" in window; + parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; + if ( !xml || parserErrorElem ) { + jQuery.error( "Invalid XML: " + ( + parserErrorElem ? + jQuery.map( parserErrorElem.childNodes, function( el ) { + return el.textContent; + } ).join( "\n" ) : + data + ) ); + } + return xml; +}; var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, @@ -8685,8 +8595,8 @@ jQuery.extend( jQuery.event, { return; } - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + // Determine event propagation path in advance, per W3C events spec (trac-9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724) if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { bubbleType = special.delegateType || type; @@ -8738,7 +8648,7 @@ jQuery.extend( jQuery.event, { acceptData( elem ) ) { // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (#6170) + // Don't do default actions on window, that's where global variables be (trac-6170) if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method @@ -8806,85 +8716,6 @@ jQuery.fn.extend( { } ); -// Support: Firefox <=44 -// Firefox doesn't have focus(in | out) events -// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 -// -// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 -// focus(in | out) events fire after focus & blur events, -// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order -// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 -if ( !support.focusin ) { - jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - attaches = dataPriv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - dataPriv.remove( doc, fix ); - - } else { - dataPriv.access( doc, fix, attaches ); - } - } - }; - } ); -} -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml, parserErrorElem; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) {} - - parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; - if ( !xml || parserErrorElem ) { - jQuery.error( "Invalid XML: " + ( - parserErrorElem ? - jQuery.map( parserErrorElem.childNodes, function( el ) { - return el.textContent; - } ).join( "\n" ) : - data - ) ); - } - return xml; -}; - - var rbracket = /\[\]$/, rCRLF = /\r?\n/g, @@ -9012,7 +8843,7 @@ var rantiCache = /([?&])_=[^&]*/, rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - // #7653, #8125, #8152: local protocol detection + // trac-7653, trac-8125, trac-8152: local protocol detection rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, @@ -9035,7 +8866,7 @@ var */ transports = {}, - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + // Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression allTypes = "*/".concat( "*" ), // Anchor tag for parsing the document origin @@ -9106,7 +8937,7 @@ function inspectPrefiltersOrTransports( structure, options, originalOptions, jqX // A special extend for ajax options // that takes "flat" options (not to be deep extended) -// Fixes #9887 +// Fixes trac-9887 function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; @@ -9517,12 +9348,12 @@ jQuery.extend( { deferred.promise( jqXHR ); // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (#10093: consistency with old signature) + // Handle falsy url in the settings object (trac-10093: consistency with old signature) // We also use the url parameter if available s.url = ( ( url || s.url || location.href ) + "" ) .replace( rprotocol, location.protocol + "//" ); - // Alias method option to type as per ticket #12004 + // Alias method option to type as per ticket trac-12004 s.type = options.method || options.type || s.method || s.type; // Extract dataTypes list @@ -9565,7 +9396,7 @@ jQuery.extend( { } // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118) fireGlobals = jQuery.event && s.global; // Watch for a new set of requests @@ -9594,7 +9425,7 @@ jQuery.extend( { if ( s.data && ( s.processData || typeof s.data === "string" ) ) { cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - // #9682: remove data so that it's not used in an eventual retry + // trac-9682: remove data so that it's not used in an eventual retry delete s.data; } @@ -9867,7 +9698,7 @@ jQuery._evalUrl = function( url, options, doc ) { return jQuery.ajax( { url: url, - // Make this explicit, since user can override this through ajaxSetup (#11264) + // Make this explicit, since user can override this through ajaxSetup (trac-11264) type: "GET", dataType: "script", cache: true, @@ -9976,7 +9807,7 @@ var xhrSuccessStatus = { 0: 200, // Support: IE <=9 only - // #1450: sometimes IE returns 1223 when it should be 204 + // trac-1450: sometimes IE returns 1223 when it should be 204 1223: 204 }, xhrSupported = jQuery.ajaxSettings.xhr(); @@ -10048,7 +9879,7 @@ jQuery.ajaxTransport( function( options ) { } else { complete( - // File: protocol always yields status 0; see #8605, #14207 + // File: protocol always yields status 0; see trac-8605, trac-14207 xhr.status, xhr.statusText ); @@ -10109,7 +9940,7 @@ jQuery.ajaxTransport( function( options ) { xhr.send( options.hasContent && options.data || null ); } catch ( e ) { - // #14683: Only rethrow if this hasn't been notified as an error yet + // trac-14683: Only rethrow if this hasn't been notified as an error yet if ( callback ) { throw e; } @@ -10729,7 +10560,9 @@ jQuery.fn.extend( { }, hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + return this + .on( "mouseenter", fnOver ) + .on( "mouseleave", fnOut || fnOver ); } } ); @@ -10753,7 +10586,9 @@ jQuery.each( // Support: Android <=4.0 only // Make sure we trim BOM and NBSP -var rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; +// Require that the "whitespace run" starts from a non-whitespace +// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position. +var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g; // Bind a function to a context, optionally partially applying any // arguments. @@ -10820,7 +10655,7 @@ jQuery.isNumeric = function( obj ) { jQuery.trim = function( text ) { return text == null ? "" : - ( text + "" ).replace( rtrim, "" ); + ( text + "" ).replace( rtrim, "$1" ); }; @@ -10868,8 +10703,8 @@ jQuery.noConflict = function( deep ) { }; // Expose jQuery and $ identifiers, even in AMD -// (#7102#comment:10, https://github.com/jquery/jquery/pull/557) -// and CommonJS for browser emulators (#13566) +// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557) +// and CommonJS for browser emulators (trac-13566) if ( typeof noGlobal === "undefined" ) { window.jQuery = window.$ = jQuery; } @@ -10881,7 +10716,7 @@ return jQuery; } ); /*! - * jQuery Migrate - v3.3.2 - 2020-11-17T23:22Z + * jQuery Migrate - v3.5.2 - 2020-11-17T23:22Z * Copyright OpenJS Foundation and other contributors */ ( function( factory ) { @@ -10906,7 +10741,7 @@ return jQuery; } )( function( jQuery, window ) { "use strict"; -jQuery.migrateVersion = "3.3.2"; +jQuery.migrateVersion = "3.5.2"; jQuery.migrateMute = true; // Returns 0 if v1 == v2, -1 if v1 < v2, 1 if v1 > v2 diff --git a/frontend/express/public/javascripts/utils/additional-methods.js b/frontend/express/public/javascripts/utils/additional-methods.js index bc1ef39ed61..ed754644b57 100644 --- a/frontend/express/public/javascripts/utils/additional-methods.js +++ b/frontend/express/public/javascripts/utils/additional-methods.js @@ -1,9 +1,9 @@ /*! - * jQuery Validation Plugin v1.19.6-pre + * jQuery Validation Plugin v1.21.0 * * https://jqueryvalidation.org/ * - * Copyright (c) 2023 Jörn Zaefferer + * Copyright (c) 2024 Jörn Zaefferer * Released under the MIT license */ (function( factory ) { diff --git a/frontend/express/public/javascripts/utils/jquery.validate.js b/frontend/express/public/javascripts/utils/jquery.validate.js index 4adbbeb34d9..e54cd1dc48f 100644 --- a/frontend/express/public/javascripts/utils/jquery.validate.js +++ b/frontend/express/public/javascripts/utils/jquery.validate.js @@ -1,9 +1,9 @@ /*! - * jQuery Validation Plugin v1.19.6-pre + * jQuery Validation Plugin v1.21.0 * * https://jqueryvalidation.org/ * - * Copyright (c) 2023 Jörn Zaefferer + * Copyright (c) 2024 Jörn Zaefferer * Released under the MIT license */ (function( factory ) { @@ -293,6 +293,7 @@ $.extend( $.validator, { onsubmit: true, ignore: ":hidden", ignoreTitle: false, + customElements: [], onfocusin: function( element ) { this.lastActive = element; @@ -440,17 +441,17 @@ $.extend( $.validator, { settings[ eventType ].call( validator, this, event ); } } - + var focusListeners = [ ":text", "[type='password']", "[type='file']", "select", "textarea", "[type='number']", "[type='search']", + "[type='tel']", "[type='url']", "[type='email']", "[type='datetime']", "[type='date']", "[type='month']", + "[type='week']", "[type='time']", "[type='datetime-local']", "[type='range']", "[type='color']", + "[type='radio']", "[type='checkbox']", "[contenteditable]", "[type='button']" ]; + var clickListeners = [ "select", "option", "[type='radio']", "[type='checkbox']" ]; $( this.currentForm ) - .on( "focusin.validate focusout.validate keyup.validate", - ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " + - "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " + - "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " + - "[type='radio'], [type='checkbox'], [contenteditable], [type='button']", delegate ) + .on( "focusin.validate focusout.validate keyup.validate", focusListeners.concat( this.settings.customElements ).join( ", " ), delegate ) // Support: Chrome, oldIE // "select" is provided as event.target when clicking a option - .on( "click.validate", "select, option, [type='radio'], [type='checkbox']", delegate ); + .on( "click.validate", clickListeners.concat( this.settings.customElements ).join( ", " ), delegate ); if ( this.settings.invalidHandler ) { $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler ); @@ -647,11 +648,12 @@ $.extend( $.validator, { elements: function() { var validator = this, - rulesCache = {}; + rulesCache = {}, + selectors = [ "input", "select", "textarea", "[contenteditable]" ]; // Select all valid inputs inside the form (no submit or reset buttons) return $( this.currentForm ) - .find( "input, select, textarea, [contenteditable]" ) + .find( selectors.concat( this.settings.customElements ).join( ", " ) ) .not( ":submit, :reset, :image, :disabled" ) .not( this.settings.ignore ) .filter( function() { @@ -1501,7 +1503,7 @@ $.extend( $.validator, { // https://jqueryvalidation.org/number-method/ number: function( value, element ) { - return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value ); + return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:-?\.\d+)?$/.test( value ); }, // https://jqueryvalidation.org/digits-method/ @@ -1612,11 +1614,12 @@ $.extend( $.validator, { param = typeof param === "string" && { url: param } || param; optionDataString = $.param( $.extend( { data: value }, param.data ) ); - if ( previous.old === optionDataString ) { + if ( previous.valid !== null && previous.old === optionDataString ) { return previous.valid; } previous.old = optionDataString; + previous.valid = null; validator = this; this.startRequest( element ); data = {}; diff --git a/frontend/express/public/localization/dashboard/dashboard.properties b/frontend/express/public/localization/dashboard/dashboard.properties index 0eaa9b14346..0b1a5340f46 100644 --- a/frontend/express/public/localization/dashboard/dashboard.properties +++ b/frontend/express/public/localization/dashboard/dashboard.properties @@ -908,6 +908,7 @@ management-users.no-role = No role management-users.create-user = Create User management-users.delete-user = Delete User management-users.edit = Click to edit +management-users.disable-2fa-user = Disable 2FA management-users.all-roles = All roles management-users.not-logged-in-yet = Not logged in yet management-users.close = Click to close diff --git a/frontend/express/public/stylesheets/vue/clyvue.scss b/frontend/express/public/stylesheets/vue/clyvue.scss index 42d00bdb798..bd02ad93279 100644 --- a/frontend/express/public/stylesheets/vue/clyvue.scss +++ b/frontend/express/public/stylesheets/vue/clyvue.scss @@ -1189,88 +1189,166 @@ } } - // colorpicker - .cly-vue-colorpicker { - $cp-preview-height: 32px; - $cp-preview-width: 138px; - $cp-preview-border-size: 1px; - $cp-picker-body-size: 251px; - $cp-picker-body-padding: 12px; - display: flex; - height: #{$cp-preview-height}; - .picker-body { - &--left{ - left:0 - } - &--right{ - left: #{-$cp-preview-width}; - } - position: relative; + // color-picker + .cly-vue-color-picker { + position: relative; + display: block; + + // .cly-vue-color-picker__body + &__body { + position: absolute; + bottom: 0; + right: 0; + z-index: 9999; + transform: translateY(calc(100% + 8px)); + box-shadow: 0 3px 7px rgba(0, 0, 0, 0.08); border: 1px solid #d0d0d0; border-radius: 2px; + width: 275px; height: 415px; + background-color: #ffffff; - top: #{$cp-preview-height}; - z-index: 9999; - > .vc-sketch { + + // .cly-vue-color-picker__body .vc-sketch + & .vc-sketch { background-color: transparent; - padding: #{$cp-picker-body-padding}; + padding: 12px; box-shadow: none; border: none; border-radius: 0; - width: #{$cp-picker-body-size}; - .vc-sketch-saturation-wrap { - height: #{$cp-picker-body-size - 180px}; - bottom: #{$cp-picker-body-padding}; - .vc-saturation { - height: #{$cp-picker-body-size}; + width: 251px; + + // .cly-vue-color-picker__body .vc-sketch .vc-sketch-saturation-wrap + & .vc-sketch-saturation-wrap { + height: calc(251px - 180px); + bottom: 12px; + + // .cly-vue-color-picker__body .vc-sketch .vc-sketch-saturation-wrap .vc-saturation + & .vc-saturation { + height: 251px; } } - .vc-sketch-presets { + + // .cly-vue-color-picker__body .vc-sketch .vc-sketch-presets + & .vc-sketch-presets { display: none; } } - .button-controls { - float: right; - padding-right: #{$cp-picker-body-padding}; + + // .cly-vue-color-picker__body--left + &--left { + right: 0; + } + + // .cly-vue-color-picker__body--right + &--right { + right: 0; + transform: translate(-50%, 100%); } } - .preview { - float: right; + + // .cly-vue-color-picker__buttons-container + &__buttons-container { + display: flex; + align-items: center; + justify-content: flex-end; + padding-right: 12px; + } + + // .cly-vue-color-picker__input + &__input { + box-sizing: border-box; + width: 100%; + height: 100%; + + padding: 6px 32px; + + border: none; outline: none; - border: #{$cp-preview-border-size} solid #CFD6E4; + color: #000; - font-size: 12px; - padding-left: 8px; + font-size: 14px; + font-weight: 400; + line-height: 20px; + + border: 1px solid #CFD6E4; border-radius: 4px; - display: flex; - box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08); - box-sizing: border-box; - width: 138px; - height: 32px; - cursor: pointer; - div { - margin-top: 5px; - position: relative; - img { - position: absolute; - } + text-transform: uppercase; + } + + // .cly-vue-color-picker__input-arrow + &__input-arrow { + position: absolute; + top: 50%; + right: 8px; + transform: translateY(-50%); + + height: 16px; + width: 16px; + + // .cly-vue-color-picker__input-arrow i + i { + font-size: 16px; + line-height: 16px; + display: flex; + align-items: center; + justify-content: center; } - .drop { - width:16px; - height:16px; - margin-right:10px; - position:absolute; - } - .color-input { - border-style:none; - width:57px; - margin-left:24px; - margin-right:24px; - font-size: 14px; + + // .cly-vue-color-picker__input-arrow .cly-vue-color-picker__input-arrow-icon-open + & .cly-vue-color-picker__input-arrow-icon-open { + display: none; + } + + // .cly-vue-color-picker__input-arrow .cly-vue-color-picker__input-arrow-icon-closed + & .cly-vue-color-picker__input-arrow-icon-closed { + display: block; } + + // .cly-vue-color-picker__input-arrow--open + &--open { + // .cly-vue-color-picker__input-arrow--open .cly-vue-color-picker__input-arrow-icon-open + & .cly-vue-color-picker__input-arrow-icon-open { + display: block; + } + + // .cly-vue-color-picker__input-arrow--open .cly-vue-color-picker__input-arrow-icon-closed + & .cly-vue-color-picker__input-arrow-icon-closed { + display: none; + } + } + } + + // .cly-vue-color-picker__input-container + &__input-container { + position: relative; + cursor: pointer; + + box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08); + + max-width: 138px; + } + + // .cly-vue-color-picker__input-drop + &__input-drop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + // .cly-vue-color-picker__input-drop-container + &__input-drop-container { + position: absolute; + top: 50%; + left: 8px; + transform: translateY(-50%); + + width: 16px; + height: 16px; } } @@ -4242,69 +4320,19 @@ } } - .cly-vue-content-builder { position: absolute; z-index: 2001; - // z-index: 2006; - // z-index: 9999999; - background-color: white; - // width: 100vw; - width: 100%; top: 0; left: 0; - display: flex; - flex-direction: column; + width: 100%; min-height: 100vh; - &__layout-step { - position: relative; - .picker-body--right { - position: absolute !important; - right: 0 !important; - left: auto !important - } - &__component { - .el-input-group__append { - padding: 0 8px !important; - } - /* .el-slider__bar { - background-color: #0166D6; - } + display: flex; + flex-direction: column; - .el-slider__button { - border-color: #0166D6; - } */ + background-color: white; - .el-slider__runway { - background-color: #E2E4E8; - } - } - &__header { - font-family: Inter; - font-size: 12px; - font-weight: 500; - line-height: 14px; - letter-spacing: 0.5px; - text-align: left; - color: #424243; - margin-bottom: 24px; - } - &__label { - font-family: Inter; - font-size: 13px; - font-weight: 500; - line-height: 16px; - text-align: left; - color: #333C48; - } - &__element { - margin-bottom: 24px; - } - &__sub-header { - font-weight: 500; - } - } &__layout-steps { padding: 0 16px; .el-collapse-item__wrap { @@ -4315,92 +4343,16 @@ } background-color: #FFF } + + // .cly-vue-content-builder__layout-header &__layout-header { - display: flex; - height: 80px; + box-sizing: border-box; + display: block; + width: 100%; + padding: 16px 24px; background-color: #FFFFFF; - align-items: center; - justify-content: space-between; - - &__wrapper { - display: flex; - justify-content: space-between; - align-items: center; - margin-left: 24px; - margin-right: 24px; - height: 80px; - background-color: #FFFFFF; - width: 100%; - } - - &__left { - display: flex; - align-items: center; - // width: 350px; - } - - &__icon { - border: 1px solid #d1d1d1; - width: 32px; - height: 32px; - border-radius: 40px; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - box-shadow: 0px 1px 2px #D1D1D1; - i { - font-weight: 600; - } - } - - &__toggle { - margin-left: 5px; - margin-right: -8px; - } - - &__info { - margin-left: 24px; - } - - &__info-title { - h4 { - margin: 0; - max-width: 400px; - } - &__input { - width: 200px; - } - } - - &__info-meta { - display: flex; - font-size: 13px; - } - - &__info-version { - text-decoration: underline; - } - - &__info-separator { - display: flex; - align-items: center; - margin: 0 5px; - i { - font-size: 6px; - } - } - - &__tabs { - width: 350px; - } + } - &__actions { - display: flex; - align-items: center; - gap: 16px; - } - } &__layout-main { margin: 0px 24px 24px 24px; border: 1px solid #ECECEC; @@ -4480,38 +4432,419 @@ } } -.cly-option-swapper { +.cly-vue-content-builder-header { display: flex; - justify-content: space-around; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + background-color: #FFFFFF; - &__each-box-wrapper { - width: 100%; + // .cly-vue-content-builder-header__actions + &__actions { + display: flex; + align-items: center; + justify-content: flex-end; } - &__each { + // .cly-vue-content-builder-header__close-button + &__close-button { + cursor: pointer; display: flex; align-items: center; justify-content: center; - height: 32px; - border: 1px solid #E2E4E8; - text-align: center; + padding: 8px; + border: 1px solid #D1D1D1; + border-radius: 40px; + box-shadow: 0px 1px 2px #D1D1D1; + margin-right: 24px; + + // .cly-vue-content-builder-header__close-button i + i { + color: #616165; + font-weight: 700; + line-height: 16px; + } + } + + // .cly-vue-content-builder-header__info + &__info { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + } + + // .cly-vue-content-builder-header__info-meta + &__info-meta { + display: flex; + align-items: center; + justify-content: flex-start; + + margin-top: 6px; + + color: #333C48; + font-size: 12px; + font-weight: 400; + line-height: 16px; + } + + // .cly-vue-content-builder-header__input + &__input { + // .cly-vue-content-builder-header__input .el-input__inner + & .el-input__inner { + height: initial; + padding: 0; + + background: transparent; + border: none; + border-bottom: 1px solid transparent; + outline: none; + border-radius: 0; + + font-size: 16px; + font-weight: 500; + line-height: 24px; + color: #333C48; + + // .cly-vue-content-builder-header__input .el-input__inner:focus + &:focus { + box-shadow: none; + } + } + + // .cly-vue-content-builder-header__input:hover .el-input__inner + // .cly-vue-content-builder-header__input--editing .el-input__inner + &:hover .el-input__inner, + &--editing .el-input__inner { + border-color: #333C48; + } + } + + // .cly-vue-content-builder-header__input-container + &__input-container { + display: block; cursor: pointer; } - &__active { - background-color: #0166D6; - border: 1px solid #0166D6; - border-right: 1px solid #0166d6 !important; + // .cly-vue-content-builder-header__left + &__left { + display: flex; + align-items: center; + justify-content: flex-start; + } + + // .cly-vue-content-builder-header__options-button + &__options-button { + margin-left: 8px; + + // .cly-vue-content-builder-header__options-button.cly-vue-more-options .el-button + &.cly-vue-more-options .el-button { + padding: 8px; + border-radius: 8px; + box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.05); + } } - &__first { - border-top-left-radius: 6px; - border-bottom-left-radius: 6px; + // .cly-vue-content-builder-header__save-button + &__save-button { + border-radius: 8px; + + // .cly-vue-content-builder-header__badge + .cly-vue-content-builder-header__save-button + .cly-vue-content-builder-header__badge + & { + margin-left: 16px; + } } - &__last { - border-top-right-radius: 6px; - border-bottom-right-radius: 6px; + // .cly-vue-content-builder-header__tabs + &__tabs { + // .cly-vue-content-builder-header__tabs .cly-vue-tabs__primary-tab-list + & .cly-vue-tabs__primary-tab-list { + padding: 0; + } + + // .cly-vue-content-builder-header__tabs .cly-vue-tabs__tab + & .cly-vue-tabs__tab { + padding: 8px 12px; + border-radius: 8px; + + font-size: 14px; + font-weight: 500; + line-height: 20px; + + // .cly-vue-content-builder-header__tabs .cly-vue-tabs__tab:not(:first-child) + &:not(:first-child) { + margin-left: 8px; + } + } + } + + // .cly-vue-content-builder-header__toggle + &__toggle { + margin-right: 16px; + } + + // .cly-vue-content-builder-header__version + &__version { + text-decoration: underline; + + // .cly-vue-content-builder-header__version + .cly-vue-content-builder-header__created-by + & + .cly-vue-content-builder-header__created-by { + position: relative; + margin-left: 12px; + + // .cly-vue-content-builder-header__version + .cly-vue-content-builder-header__created-by::before + &::before { + content: '·'; + position: absolute; + top: 0; + left: -4px; + transform: translateX(-50%); + height: 16px; + width: 4px; + } + } + } +} + +.cly-vue-content-builder-sidebar-input { + position: relative; + + // .cly-vue-content-builder-sidebar-input__component + &__component { + max-width: 50%; + width: auto; + box-shadow: none; + + // .cly-vue-content-builder-sidebar-input__component.el-input-number + &.el-input-number { + position: relative; + + // .cly-vue-content-builder-sidebar-input__component.el-input-number .el-input__inner + & .el-input__inner { + text-align: left; + padding: 6px 10px; + padding-right: 34px; + } + } + + // .cly-vue-content-builder-sidebar-input__component .el-input__count + & .el-input__count { + position: absolute; + top: 0; + right: 0; + transform: translateY(-100%); + background: transparent; + font-size: 13px; + font-weight: 400; + line-height: 16px; + + // .cly-vue-content-builder-sidebar-input__component .el-input__count .el-input__count-inner + & .el-input__count-inner { + padding: 0; + background: transparent; + } + } + + // .cly-vue-content-builder-sidebar-input__component .el-input__inner + & .el-input__inner { + font-size: 14px; + font-weight: 400; + line-height: 20px; + color: #383A3F; + + background-color: #FFFFFF; + border: 1px solid #E2E4E8; + border-radius: 6px; + padding: 6px 10px; + + // .cly-vue-content-builder-sidebar-input__component .el-input__inner:hover + &:hover { + border-color: #0166D6; + } + } + + // .cly-vue-content-builder-sidebar-input__component .el-input__suffix + & .el-input__suffix { + right: 0; + } + + // .cly-vue-content-builder-sidebar-input__component .el-input__suffix-inner + & .el-input__suffix-inner { + padding: 6px 10px; + padding-left: 0; + color: #81868D; + width: 16px; + } + + // .cly-vue-content-builder-sidebar-input__component.el-input--suffix + &.el-input--suffix { + // .cly-vue-content-builder-sidebar-input__component.el-input--suffix .el-input__inner + & .el-input__inner { + padding-right: 34px; + } + } + + // .cly-vue-content-builder-sidebar-input__component--slider + &--slider { + width: 100%; + + // .cly-vue-content-builder-sidebar-input__component--slider .el-slider__bar + & .el-slider__bar { + background-color: #0166D6; + } + + // .cly-vue-content-builder-sidebar-input__component--slider .el-slider__button + & .el-slider__button { + border: 1px solid #0166D6; + } + + // .cly-vue-content-builder-sidebar-input__component--slider .el-slider__button-wrapper + & .el-slider__button-wrapper { + z-index: 0; + } + + // .cly-vue-content-builder-sidebar-input__component--slider .el-slider__runway + & .el-slider__runway { + margin: 0; + background-color: #E2E4E8; + } + } + } + + // .cly-vue-content-builder-sidebar-input__input-container + &__input-container { + display: flex; + justify-content: flex-start; + align-items: center; + flex-wrap: wrap; + gap: 8px; + + // .cly-vue-content-builder-sidebar-input__input-container--small .cly-vue-content-builder-sidebar-input__component + &--small .cly-vue-content-builder-sidebar-input__component { + max-width: 70px; + } + + // .cly-vue-content-builder-sidebar-input__input-container--large .cly-vue-content-builder-sidebar-input__component + &--large .cly-vue-content-builder-sidebar-input__component { + max-width: 100%; + } + + // .cly-vue-content-builder-sidebar-input__input-container--vertical + &--vertical { + flex-direction: column; + align-items: flex-start; + + // .cly-vue-content-builder-sidebar-input__input-container--vertical .cly-vue-content-builder-sidebar-input__component + & .cly-vue-content-builder-sidebar-input__component { + width: 100%; + max-width: 100%; + } + } + } + + // .cly-vue-content-builder-sidebar-input__label + &__label { + display: flex; + align-items: baseline; + justify-content: flex-start; + + flex-shrink: 0; + margin-right: auto; + font-size: 13px; + font-weight: 500; + line-height: 16px; + + // .cly-vue-content-builder-sidebar-input__label i + i { + margin-left: 4px; + font-size: 13px; + font-weight: 500; + line-height: 16px; + } + } + + // .cly-vue-content-builder-sidebar-input__number-input-suffix + &__number-input-suffix { + position: absolute; + top: 0; + right: 0; + padding: 6px 10px; + padding-left: 0; + color: #81868D; + width: 16px; + font-size: 14px; + line-height: 20px; + } + + // .cly-vue-content-builder-sidebar-input__sub-header + &__sub-header { + color: #81868D; + font-size: 13px; + font-weight: 500; + line-height: 16px; + margin-bottom: 16px; + } +} + +.cly-option-swapper { + display: flex; + align-items: center; + justify-content: center; + + // .cly-option-swapper__option + &__option { + cursor: pointer; + padding: 8px 10px; + border: 1px solid #E2E4E8; + font-size: 13px; + font-weight: 400; + line-height: 16px; + color: #333C48; + background: #FFFFFF; + flex-grow: 1; + text-align: center; + + // .cly-option-swapper__option--active:not(.cly-option-swapper__option--disabled):not(.cly-option-swapper__option--no-highlight) + &--active:not(#{&}--disabled):not(#{&}--no-highlight) { + background: rgba(#0166D6, 0.1); + border-color: #0166D6; + color: #0166D6; + } + + // .cly-option-swapper__option--disabled + &--disabled { + cursor: not-allowed; + background: #E2E4E8; + opacity: 0.5; + + // .cly-option-swapper__option--disabled.has-tooltip + &.has-tooltip { + cursor: not-allowed; + } + } + + // .cly-option-swapper__option--first + &--first { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; + } + + // .cly-option-swapper__option--last + &--last { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + } + + // .cly-option-swapper__option--disabled.cly-option-swapper__option--active + &--no-highlight#{&}--active { + background: #F6F6F6; + } + } + + // .cly-option-swapper--disabled + &--disabled { + cursor: not-allowed; + opacity: 0.5; } } @@ -4564,3 +4897,12 @@ border-radius: 8px; } } + +.cly-event-select { + &__loading { + display: flex; + margin-top:0; + position: unset; + top:0; + } +} diff --git a/frontend/express/views/dashboard.html b/frontend/express/views/dashboard.html index 3391dd7aa3a..cba8a7606c0 100644 --- a/frontend/express/views/dashboard.html +++ b/frontend/express/views/dashboard.html @@ -1887,145 +1887,180 @@

{{this.nam <% if (!offline_mode) { %> - <% if (!track || track == "GA" && member['global_admin'] || track == "noneGA" && !member['global_admin']) { %> - - - + - <% } %> + + Countly.userData.set("lastServer", window.location.hostname); + Countly.userData.set("lastVersion", "<%= countlyVersion %>"); + Countly.userData.set("lastEdition", "<%= countlyTypeTrack %>"); + Countly.userData.set("isTrial", <%= countlyTrial %>); + Countly.userData.set("cpus", <%= cpus %>); + Countly.userData.push_unique("servers", window.location.hostname); + Countly.userData.push_unique("versions", "<%= countlyVersion %>"); + Countly.userData.push_unique("editions", "<%= countlyTypeTrack %>"); + Countly.userData.save(); + + <% if (installed) { %> + Countly.add_event({ + key:"INSTALL" + }); + <% } %> + } + + if (countlyGlobal.config.self_tracking) { + //initializing countly with params + var Countly2 = Countly.init({ + app_key: countlyGlobal.config.self_tracking, + url: window.location.origin, + device_id: countlyGlobal.member.email, + app_version: "<%= countlyVersion %>", + interval:1000 + }); + + //track sessions automatically + Countly2.track_sessions(); + + //track pageviews automatically + Countly2.track_pageview(Countly.getViewName()); + + $(window).on('hashchange', function() { + Countly2.track_pageview(Countly.getViewName()); + }); + + //display in app messages + if (Countly2.content && Countly2.content.enterContentZone) { + Countly2.content.enterContentZone(); + } + + Countly2.user_details({ + "name": countlyGlobal.member.full_name || "", + "username": countlyGlobal.member.username || "", + "email": countlyGlobal.member.email + }); + } + <% } %>