diff --git a/.github/workflows/issuesToProject.yml b/.github/workflows/issuesToProject.yml deleted file mode 100644 index fc76f25944..0000000000 --- a/.github/workflows/issuesToProject.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Add issues to issues project - -on: - issues: - types: - - opened - -jobs: - add-to-project: - name: Add issue to project - runs-on: ubuntu-latest - steps: - - uses: actions/add-to-project@main - with: - project-url: https://github.com/orgs/dodona-edu/projects/2 - github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} diff --git a/Gemfile b/Gemfile index 359b6026c4..6d3a3aa11a 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem 'rails', '~> 7.0.4' # Use mysql as the database for Active Record gem 'mysql2', '~> 0.5.5' # Use Puma as the app server -gem 'puma', '~> 6.1.1' +gem 'puma', '~> 6.2.2' # Use dart-sass for stylesheets gem 'cssbundling-rails', '~> 1.1.2' @@ -51,7 +51,7 @@ gem 'will_paginate', '~>3.3.1' # markdown rendering and syntax highlighting gem 'kramdown', '~>2.4.0' gem 'kramdown-parser-gfm', '~>1.1.0' -gem 'rouge', '4.1.0' +gem 'rouge', '4.1.1' # feedback table builder gem 'builder', '~>3.2.4' @@ -66,13 +66,13 @@ gem 'ace-rails-ap', '~>4.5' gem 'autoprefixer-rails', '~>10.4.13' # saml authentication -gem 'devise', '~>4.9.0' +gem 'devise', '~>4.9.2' gem 'ruby-saml', '~> 1.15.0' # omniauth gem 'omniauth-google-oauth2', '~> 1.1.1' gem 'omniauth-oauth2', '~> 1.8.0' -gem 'omniauth_openid_connect', '~> 0.6.1' +gem 'omniauth_openid_connect', '~> 0.7.1' gem 'omniauth-rails_csrf_protection', '~> 1.0.1' # Json webtokens @@ -105,8 +105,8 @@ gem 'bcrypt_pbkdf' gem 'ed25519' # i18n -gem 'i18n-js', '~> 4.2.2' -gem 'rails-i18n', '~> 7.0.6' +gem 'i18n-js', '~> 4.2.3' +gem 'rails-i18n', '~> 7.0.7' # email exceptions gem 'exception_notification', '~> 4.5.0' @@ -114,7 +114,7 @@ gem 'httparty', '~> 0.21.0' gem 'slack-notifier', '~> 2.4.0' # css styles for emails -gem 'nokogiri', '~> 1.14.2' +gem 'nokogiri', '~> 1.14.4' gem 'premailer-rails', '~> 1.12.0' # filtering @@ -127,15 +127,15 @@ gem 'rubyzip', '~> 2.3.2' gem 'dalli', '~> 3.2.4' # Generate 'random' values like usernames, emails, ... -gem 'faker', '~> 3.1.1' +gem 'faker', '~> 3.2.0' # Profiling gem 'flamegraph', '~> 0.9.5' gem 'memory_profiler', '~> 1.0.1' -gem 'rack-mini-profiler', '~> 3.0.0' -gem 'stackprof', '~> 0.2.24' +gem 'rack-mini-profiler', '~> 3.1.0' +gem 'stackprof', '~> 0.2.25' -gem 'ddtrace', '~> 1.10.1' +gem 'ddtrace', '~> 1.11.1' # Make sure filesystem changes only happen at the end of a transaction gem 'after_commit_everywhere', '~> 1.3.0' @@ -156,8 +156,8 @@ group :development, :test do gem 'byebug', '~> 11.1.3', platforms: %i[mri mingw x64_mingw] # Adds support for Capybara system testing and selenium driver - gem 'capybara', '~> 3.38.0' - gem 'selenium-webdriver', '~> 4.8.3' + gem 'capybara', '~> 3.39.1' + gem 'selenium-webdriver', '~> 4.9.1' end group :test do @@ -180,7 +180,7 @@ group :development do gem 'web-console', '~> 4.2.0' gem 'rb-readline', '~> 0.5.5' # require for irb - gem 'rubocop-rails', '~> 2.18.0' + gem 'rubocop-rails', '~> 2.19.1' # for opening letters gem 'letter_opener', '~> 1.8.1' diff --git a/Gemfile.lock b/Gemfile.lock index 5b078420b6..d1d8a29544 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -67,7 +67,7 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) - addressable (2.8.1) + addressable (2.8.4) public_suffix (>= 2.0.2, < 6.0) aes_key_wrap (1.1.0) after_commit_everywhere (1.3.0) @@ -110,7 +110,7 @@ GEM capistrano3-delayed-job (1.7.6) capistrano (~> 3.0, >= 3.0.0) daemons (~> 1.3) - capybara (3.38.0) + capybara (3.39.1) addressable matrix mini_mime (>= 0.1.3) @@ -133,10 +133,10 @@ GEM daemons (1.4.1) dalli (3.2.4) date (3.3.3) - ddtrace (1.10.1) + ddtrace (1.11.1) debase-ruby_core_source (>= 0.10.16, <= 3.2.0) libdatadog (~> 2.0.0.1.0) - libddwaf (~> 1.6.2.0.0) + libddwaf (~> 1.8.2.0.0) msgpack debase-ruby_core_source (3.2.0) delayed_job (4.1.10) @@ -149,7 +149,7 @@ GEM delayed_job (> 2.0.3) rack-protection (>= 1.5.5) sinatra (>= 1.4.4) - devise (4.9.0) + devise (4.9.2) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -174,7 +174,7 @@ GEM factory_bot_rails (6.2.0) factory_bot (~> 6.2.0) railties (>= 5.0.0) - faker (3.1.1) + faker (3.2.0) i18n (>= 1.8.11, < 2) faraday (2.7.4) faraday-net_http (>= 2.0, < 3.1) @@ -199,10 +199,9 @@ GEM httparty (0.21.0) mini_mime (>= 1.0.0) multi_xml (>= 0.5.2) - httpclient (2.8.3) - i18n (1.12.0) + i18n (1.13.0) concurrent-ruby (~> 1.0) - i18n-js (4.2.2) + i18n-js (4.2.3) glob (>= 0.4.0) i18n icalendar (2.8.0) @@ -238,14 +237,14 @@ GEM letter_opener (1.8.1) launchy (>= 2.2, < 3) libdatadog (2.0.0.1.0-x86_64-linux) - libddwaf (1.6.2.0.0-x86_64-linux) + libddwaf (1.8.2.0.0-x86_64-linux) ffi (~> 1.0) listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.19.1) + loofah (2.21.2) crass (~> 1.0.2) - nokogiri (>= 1.5.9) + nokogiri (>= 1.12.0) mail (2.8.1) mini_mime (>= 0.1.1) net-imap @@ -267,7 +266,7 @@ GEM minitest mocha (2.0.2) ruby2_keywords (>= 0.0.5) - msgpack (1.6.1) + msgpack (1.7.0) multi_json (1.15.0) multi_xml (0.6.0) mustermann (3.0.0) @@ -285,8 +284,8 @@ GEM net-smtp (0.3.3) net-protocol net-ssh (7.0.1) - nio4r (2.5.8) - nokogiri (1.14.2-x86_64-linux) + nio4r (2.5.9) + nokogiri (1.14.4-x86_64-linux) racc (~> 1.4) oauth2 (2.0.8) faraday (>= 0.17.3, < 3.0) @@ -310,23 +309,25 @@ GEM omniauth-rails_csrf_protection (1.0.1) actionpack (>= 4.2) omniauth (~> 2.0) - omniauth_openid_connect (0.6.1) + omniauth_openid_connect (0.7.1) omniauth (>= 1.9, < 3) - openid_connect (~> 1.1) - openid_connect (1.4.2) + openid_connect (~> 2.2) + openid_connect (2.2.0) activemodel attr_required (>= 1.0.0) - json-jwt (>= 1.15.0) + faraday (~> 2.0) + faraday-follow_redirects + json-jwt (>= 1.16) net-smtp - rack-oauth2 (~> 1.21) - swd (~> 1.3) + rack-oauth2 (~> 2.2) + swd (~> 2.0) tzinfo validate_email validate_url - webfinger (~> 1.2) + webfinger (~> 2.0) orm_adapter (0.5.0) parallel (1.22.1) - parser (3.2.1.0) + parser (3.2.2.0) ast (~> 2.4.1) premailer (1.18.0) addressable @@ -339,18 +340,19 @@ GEM pretender (0.4.0) actionpack (>= 5.2) public_suffix (5.0.1) - puma (6.1.1) + puma (6.2.2) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) racc (1.6.2) - rack (2.2.6.4) - rack-mini-profiler (3.0.0) + rack (2.2.7) + rack-mini-profiler (3.1.0) rack (>= 1.2.0) - rack-oauth2 (1.21.3) + rack-oauth2 (2.2.0) activesupport attr_required - httpclient + faraday (~> 2.0) + faraday-follow_redirects json-jwt (>= 1.11.0) rack (>= 2.1.0) rack-protection (3.0.4) @@ -380,7 +382,7 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.5.0) loofah (~> 2.19, >= 2.19.1) - rails-i18n (7.0.6) + rails-i18n (7.0.7) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) railties (7.0.4.3) @@ -396,29 +398,29 @@ GEM rb-inotify (0.10.1) ffi (~> 1.0) rb-readline (0.5.5) - regexp_parser (2.7.0) + regexp_parser (2.8.0) responders (3.1.0) actionpack (>= 5.2) railties (>= 5.2) rexml (3.2.5) - rouge (4.1.0) - rubocop (1.46.0) + rouge (4.1.1) + rubocop (1.50.2) json (~> 2.3) parallel (~> 1.10) parser (>= 3.2.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.26.0, < 2.0) + rubocop-ast (>= 1.28.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.26.0) + rubocop-ast (1.28.0) parser (>= 3.2.1.0) - rubocop-rails (2.18.0) + rubocop-rails (2.19.1) activesupport (>= 4.2.0) rack (>= 1.1) rubocop (>= 1.33.0, < 2.0) - ruby-progressbar (1.11.0) + ruby-progressbar (1.13.0) ruby-saml (1.15.0) nokogiri (>= 1.13.10) rexml @@ -426,7 +428,7 @@ GEM ffi (~> 1.12) ruby2_keywords (0.0.5) rubyzip (2.3.2) - selenium-webdriver (4.8.3) + selenium-webdriver (4.9.1) rexml (~> 3.2, >= 3.2.5) rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) @@ -458,15 +460,16 @@ GEM sshkit (1.21.2) net-scp (>= 1.1.2) net-ssh (>= 2.8.0) - stackprof (0.2.24) - swd (1.3.0) + stackprof (0.2.25) + swd (2.0.2) activesupport (>= 3) attr_required (>= 0.0.5) - httpclient (>= 2.4) - terser (1.1.14) + faraday (~> 2.0) + faraday-follow_redirects + terser (1.1.15) execjs (>= 0.3.0, < 3) test-prof (1.2.1) - thor (1.2.1) + thor (1.2.2) tilt (2.0.11) timeout (0.3.2) tzinfo (2.0.6) @@ -487,9 +490,10 @@ GEM activemodel (>= 6.0.0) bindex (>= 0.4.0) railties (>= 6.0.0) - webfinger (1.2.0) + webfinger (2.1.2) activesupport - httpclient (>= 2.4) + faraday (~> 2.0) + faraday-follow_redirects webmock (3.18.1) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -501,7 +505,7 @@ GEM will_paginate (3.3.1) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.7) + zeitwerk (2.6.8) PLATFORMS x86_64-linux @@ -520,25 +524,25 @@ DEPENDENCIES capistrano-rvm (~> 0.1.2) capistrano-yarn (~> 2.0.2) capistrano3-delayed-job (~> 1.7.6) - capybara (~> 3.38.0) + capybara (~> 3.39.1) counter_culture (~> 3.2) cssbundling-rails (~> 1.1.2) dalli (~> 3.2.4) - ddtrace (~> 1.10.1) + ddtrace (~> 1.11.1) delayed_job_active_record (~> 4.1.7) delayed_job_web (~> 1.4.4) - devise (~> 4.9.0) + devise (~> 4.9.2) diff-lcs (~> 1.5) docker-api (~> 2.2.0) ed25519 exception_notification (~> 4.5.0) factory_bot_rails (~> 6.2.0) - faker (~> 3.1.1) + faker (~> 3.2.0) flamegraph (~> 0.9.5) has_scope (~> 0.8.1) hcaptcha (~> 7.1.0) httparty (~> 0.21.0) - i18n-js (~> 4.2.2) + i18n-js (~> 4.2.3) icalendar (~> 2.8) image_processing (~> 1.12.2) jbuilder (~> 2.11.5) @@ -556,30 +560,30 @@ DEPENDENCIES minitest-utils (~> 0.4.8) mocha (~> 2.0.2) mysql2 (~> 0.5.5) - nokogiri (~> 1.14.2) + nokogiri (~> 1.14.4) omniauth-google-oauth2 (~> 1.1.1) omniauth-oauth2 (~> 1.8.0) omniauth-rails_csrf_protection (~> 1.0.1) - omniauth_openid_connect (~> 0.6.1) + omniauth_openid_connect (~> 0.7.1) premailer-rails (~> 1.12.0) pretender (~> 0.4.0) - puma (~> 6.1.1) + puma (~> 6.2.2) pundit (~> 2.3.0) - rack-mini-profiler (~> 3.0.0) + rack-mini-profiler (~> 3.1.0) rails (~> 7.0.4) rails-controller-testing (~> 1.0.5) - rails-i18n (~> 7.0.6) + rails-i18n (~> 7.0.7) rb-readline (~> 0.5.5) - rouge (= 4.1.0) - rubocop-rails (~> 2.18.0) + rouge (= 4.1.1) + rubocop-rails (~> 2.19.1) ruby-saml (~> 1.15.0) rubyzip (~> 2.3.2) - selenium-webdriver (~> 4.8.3) + selenium-webdriver (~> 4.9.1) simplecov (~> 0.22.0) simplecov-cobertura (~> 2.1.0) slack-notifier (~> 2.4.0) sprockets-rails (~> 3.4.2) - stackprof (~> 0.2.24) + stackprof (~> 0.2.25) terser (>= 1.1.1) test-prof (~> 1.2.1) tzinfo-data diff --git a/app/assets/javascripts/code_listing.ts b/app/assets/javascripts/code_listing.ts new file mode 100644 index 0000000000..501a2435f4 --- /dev/null +++ b/app/assets/javascripts/code_listing.ts @@ -0,0 +1,81 @@ +import { CodeListingRow } from "components/annotations/code_listing_row"; +import { render } from "lit"; +import { userAnnotationState } from "state/UserAnnotations"; +import { MachineAnnotationData, machineAnnotationState } from "state/MachineAnnotations"; +import { courseState } from "state/Courses"; +import { userState } from "state/Users"; +import { submissionState } from "state/Submissions"; +import "components/annotations/annotation_options"; +import "components/annotations/annotations_count_badge"; +import { annotationState } from "state/Annotations"; +import { exerciseState } from "state/Exercises"; +import { triggerSelectionEnd } from "components/annotations/select"; + +const MARKING_CLASS = "marked"; + +function initAnnotations(submissionId: number, courseId: number, exerciseId: number, userId: number, code: string, codeLines: number, questionMode = false): void { + userAnnotationState.reset(); + submissionState.code = code; + courseState.id = courseId; + exerciseState.id = exerciseId; + userState.id = userId; + submissionState.id = submissionId; + annotationState.isQuestionMode = questionMode; + + const table = document.querySelector("table.code-listing"); + const rows = table.querySelectorAll("tr"); + + for (let i = 0; i < rows.length; i++) { + const code = rows[i].querySelector("td.rouge-code > pre").innerHTML; + const codeListingRow = new CodeListingRow(); + codeListingRow.row = i + 1; + codeListingRow.renderedCode = code; + render(codeListingRow, rows[i].parentElement, { renderBefore: rows[i] }); + rows[i].remove(); + } +} + +function addMachineAnnotations(data: MachineAnnotationData[]): void { + machineAnnotationState.setMachineAnnotations(data); +} + +function initAnnotateButtons(): void { + userState.addPermission("annotation.create"); + + document.addEventListener("pointerup", () => triggerSelectionEnd()); +} + +function loadUserAnnotations(): void { + userAnnotationState.fetch(submissionState.id); + // only show important annotations if any user annotations exist + annotationState.visibility = "important"; +} + + +// ///////////////////////////////////////////////////////////////////////// +// Highlighting //////////////////////////////////////////////////////////// +// ///////////////////////////////////////////////////////////////////////// + +function clearHighlights(): void { + const markedAnnotations = document.querySelectorAll(`tr.lineno.${MARKING_CLASS}`); + markedAnnotations.forEach(markedAnnotation => { + markedAnnotation.classList.remove(MARKING_CLASS); + }); +} + +function highlightLine(lineNr: number, scrollToLine = false): void { + const toMarkAnnotationRow = document.querySelector(`tr.lineno#line-${lineNr}`); + toMarkAnnotationRow.classList.add(MARKING_CLASS); + if (scrollToLine) { + toMarkAnnotationRow.scrollIntoView({ block: "center" }); + } +} + +export default { + initAnnotations, + addMachineAnnotations, + initAnnotateButtons, + loadUserAnnotations, + clearHighlights, + highlightLine, +}; diff --git a/app/assets/javascripts/code_listing/annotation.ts b/app/assets/javascripts/code_listing/annotation.ts deleted file mode 100644 index e63c8b8046..0000000000 --- a/app/assets/javascripts/code_listing/annotation.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { NewSavedAnnotation } from "components/saved_annotations/new_saved_annotation"; -import { isBetaCourse } from "saved_annotation_beta"; -import { SavedAnnotationIcon } from "components/saved_annotations/saved_annotation_icon"; - -export type AnnotationType = "error" | "info" | "user" | "warning" | "question"; -export type QuestionState = "unanswered" | "answered" | "in_progress"; - -export abstract class Annotation { - private static idCounter = 0; - - protected __html: HTMLDivElement | null; - - public readonly __id; - public readonly line: number | null; - public readonly text: string; - public readonly type: AnnotationType; - public readonly id: number; - - protected constructor(line: number | null, text: string, type: AnnotationType) { - this.__html = null; - this.__id = Annotation.idCounter++; - this.line = line; - this.text = text; - this.type = type; - } - - protected get body(): HTMLDivElement { - const body = document.createElement("div") as HTMLDivElement; - body.classList.add("annotation-text"); - body.innerHTML = this.text; - return body; - } - - protected get class(): string | null { - return null; - } - - protected edit(): void { - // Do nothing. - } - - public get global(): boolean { - return this.line === null; - } - - public hide(): void { - this.__html.classList.add("hide"); - } - - private get header(): HTMLDivElement { - const header = document.createElement("div") as HTMLDivElement; - header.classList.add("annotation-header"); - - // Metadata. - const meta = document.createElement("span") as HTMLSpanElement; - meta.classList.add("annotation-meta"); - meta.textContent = this.meta; - header.appendChild(meta); - - if (!this.visible) { - const icon = document.createElement("i"); - icon.classList.add("mdi", "mdi-eye-off", "mdi-18", "annotation-meta-icon"); - icon.title = I18n.t("js.user_annotation.not_released"); - meta.appendChild(icon); - } - - if (this.hasNotice) { - const span = document.createElement("span"); - span.textContent = " ยท"; - const url = document.createElement("a"); - url.href = this.noticeUrl; - url.setAttribute("target", "_blank"); - if (this.useNoticeIcon) { - const icon = document.createElement("i"); - icon.classList.add("mdi", "mdi-information", "mdi-18", "colored-info", "annotation-warning"); - icon.dataset.bsToggle = "tooltip"; - icon.dataset.bsPlacement = "top"; - icon.title = this.noticeInfo; - url.appendChild(icon); - } else { - url.textContent = " " + this.noticeInfo; - } - span.appendChild(url); - meta.appendChild(span); - } - - // Update button. - if (this.modifiable) { - const editLink = document.createElement("a"); - editLink.addEventListener("click", () => this.edit()); - editLink.classList.add("btn", "btn-icon", "annotation-control-button", "annotation-edit"); - editLink.title = this.editTitle; - - const editIcon = document.createElement("i"); - editIcon.classList.add("mdi", "mdi-pencil"); - editLink.appendChild(editIcon); - - header.appendChild(editLink); - - // REMOVE IF AFTER CLOSED BETA - if (isBetaCourse(this.courseId)) { - // Add button to create saved annotation - const saveLink = new NewSavedAnnotation(); - saveLink.fromAnnotationId = this.id; - saveLink.annotationText = this.rawText; - saveLink.savedAnnotationId = this.savedAnnotationId; - - header.appendChild(saveLink); - - // Add icon to signify saved annotation - const icon = new SavedAnnotationIcon(); - icon.savedAnnotationId = this.savedAnnotationId; - - // update icon if annotation is saved - saveLink.addEventListener("created", (e: CustomEvent) => { - icon.savedAnnotationId = e.detail.id; - }); - - meta.appendChild(icon); - } - } - - if (this.transitionable("answered")) { - const link = document.createElement("a"); - link.addEventListener("click", () => this.transition("answered")); - link.classList.add("btn", "btn-icon", "question-control-button", "question-resolve"); - link.title = I18n.t("js.user_question.resolve"); - - const icon = document.createElement("i"); - icon.classList.add("mdi", "mdi-check"); - link.appendChild(icon); - - header.appendChild(link); - } - - if (this.transitionable("in_progress")) { - const link = document.createElement("a"); - link.addEventListener("click", () => this.transition("in_progress")); - link.classList.add("btn", "btn-icon", "question-control-button", "question-in_progress"); - link.title = I18n.t("js.user_question.in_progress"); - - const icon = document.createElement("i"); - icon.classList.add("mdi", "mdi-comment-processing-outline"); - link.appendChild(icon); - - header.appendChild(link); - } - - if (this.transitionable("unanswered")) { - const link = document.createElement("a"); - link.addEventListener("click", () => this.transition("unanswered")); - link.classList.add("btn", "btn-icon", "question-control-button", "question-unresolve"); - link.title = I18n.t("js.user_question.unresolve"); - - const icon = document.createElement("i"); - icon.classList.add("mdi", "mdi-restart"); - link.appendChild(icon); - - header.appendChild(link); - } - - return header; - } - - get html(): HTMLDivElement { - if (this.__html === null) { - // Generate a new element. - this.__html = document.createElement("div") as HTMLDivElement; - this.__html.classList.add("annotation", this.type); - if (this.class !== null) { - this.__html.classList.add(this.class); - } - this.__html.id = `annotation-div-${this.__id}`; - this.__html.title = this.title; - - // Body. - this.__html.appendChild(this.header); - this.__html.appendChild(this.body); - // Ask MathJax to search for math in the annotations - window.MathJax.typeset(); - } - - return this.__html; - } - - get important(): boolean { - return this.type === "error" || this.type === "user" || this.type == "question"; - } - - protected abstract get meta(): string; - - public get modifiable(): boolean { - return false; - } - - public transitionable(to: QuestionState): boolean { - return false; - } - - public get rawText(): string { - return this.text; - } - - public get savedAnnotationId(): number | null { - return null; - } - - public get removable(): boolean { - return false; - } - - public get visible(): boolean { - return true; - } - - public async remove(): Promise { - this.__html.remove(); - } - - public show(): void { - this.__html.classList.remove("hide"); - } - - protected abstract get title(): string; - - protected get editTitle(): string { - return ""; - } - - protected get hasNotice(): boolean { - return false; - } - - protected get noticeUrl(): string | null { - return null; - } - - protected get noticeInfo(): string | null { - return null; - } - - protected get useNoticeIcon(): boolean { - return true; - } - - // REMOVE AFTER CLOSED BETA - protected get courseId(): number { - return undefined; - } - - public async update(data): Promise { - // Do nothing. - return data; - } - - protected async transition(to: QuestionState): Promise { - // Do nothing. - } -} diff --git a/app/assets/javascripts/code_listing/code_listing.ts b/app/assets/javascripts/code_listing/code_listing.ts deleted file mode 100644 index 52fbcd25a7..0000000000 --- a/app/assets/javascripts/code_listing/code_listing.ts +++ /dev/null @@ -1,524 +0,0 @@ -import { initTooltips } from "util.js"; -import { Annotation, AnnotationType } from "code_listing/annotation"; -import { MachineAnnotation, MachineAnnotationData } from "code_listing/machine_annotation"; -import { - UserAnnotation, - UserAnnotationData, - UserAnnotationFormData -} from "code_listing/user_annotation"; -import { createUserAnnotation, getAllUserAnnotations } from "code_listing/question_annotation"; -import "components/saved_annotations/saved_annotation_input"; -import { AnnotationForm } from "components/annotations/annotation_form"; -import { createSavedAnnotation, invalidateSavedAnnotation } from "state/SavedAnnotations"; - -const annotationGlobalAdd = "#add_global_annotation"; -const annotationsGlobal = "#feedback-table-global-annotations"; -const annotationsGlobalGroups = "#feedback-table-global-annotations-list"; -const annotationHideAll = "#hide_all_annotations"; -const annotationShowAll = "#show_all_annotations"; -const annotationShowErrors = "#show_only_errors"; -const annotationToggles = "#annotations_toggles"; -const badge = "#badge_code"; - -type OrderGroup = "error" | "conversation" | "warning" | "info"; - -// Map in which annotation group the annotation should appear. -const GROUP_MAPPING: Record = { - "error": "error", - "user": "conversation", - "warning": "warning", - "info": "info", - "question": "conversation" -}; - -// Order the groups. We use the same order as appearance in the mapping. -const GROUP_ORDER: OrderGroup[] = Array.from(new Set(Object.values(GROUP_MAPPING))); - -export class CodeListing { - private readonly annotations: Map; - - public readonly code: string; - public readonly codeLines: number; - public readonly submissionId: number; - public readonly courseId: number | null; - public readonly exerciseId: number; - public readonly userId: number; - - private readonly markingClass: string = "marked"; - private evaluationId: number; - - private readonly badge: HTMLSpanElement; - private readonly table: HTMLTableElement; - - private readonly globalAnnotations: HTMLDivElement; - private readonly globalAnnotationGroups: HTMLDivElement; - private readonly hideAllAnnotations: HTMLButtonElement; - private readonly showAllAnnotations: HTMLButtonElement; - private readonly showErrorAnnotations: HTMLButtonElement; - private readonly annotationToggles: HTMLDivElement; - - private readonly questionMode: boolean; - - constructor(submissionId: number, courseId: number, exerciseId: number, userId: number, code: string, codeLines: number, questionMode = false) { - this.annotations = new Map(); - this.code = code; - this.codeLines = codeLines; - this.submissionId = submissionId; - this.courseId = courseId; - this.exerciseId = exerciseId; - this.userId = userId; - this.questionMode = questionMode; - - this.badge = document.querySelector(badge); - this.table = document.querySelector("table.code-listing"); - - this.hideAllAnnotations = document.querySelector(annotationHideAll); - this.showAllAnnotations = document.querySelector(annotationShowAll); - this.showErrorAnnotations = document.querySelector(annotationShowErrors); - this.annotationToggles = document.querySelector(annotationToggles); - - this.globalAnnotations = document.querySelector(annotationsGlobal); - this.globalAnnotationGroups = document.querySelector(annotationsGlobalGroups); - - this.initAnnotations(); - } - - setEvaluation(id: number): void { - this.evaluationId = id; - } - - // ///////////////////////////////////////////////////////////////////////// - // Highlighting //////////////////////////////////////////////////////////// - // ///////////////////////////////////////////////////////////////////////// - - clearHighlights(): void { - const markedAnnotations = this.table.querySelectorAll(`tr.lineno.${this.markingClass}`); - markedAnnotations.forEach(markedAnnotation => { - markedAnnotation.classList.remove(this.markingClass); - }); - } - - highlightLine(lineNr: number, scrollToLine = false): void { - const toMarkAnnotationRow = this.table.querySelector(`tr.lineno#line-${lineNr}`); - toMarkAnnotationRow.classList.add(this.markingClass); - if (scrollToLine) { - toMarkAnnotationRow.scrollIntoView({ block: "center" }); - } - } - - // ///////////////////////////////////////////////////////////////////////// - // Annotation management /////////////////////////////////////////////////// - // ///////////////////////////////////////////////////////////////////////// - - public addAnnotation(annotation: Annotation): void { - const line = annotation.global ? 0 : annotation.line; - - if (!this.annotations.has(line)) { - this.annotations.set(line, []); - } - - // Add the annotation in the map. - this.annotations.get(line).push(annotation); - - // Append the HTML component of the annotation to the code table. - if (annotation.global) { - this.appendAnnotationToGlobal(annotation); - } else { - this.appendAnnotationToTable(annotation); - } - - this.updateViewState(); - } - - private addMachineAnnotation(data: MachineAnnotationData): void { - const annotation = new MachineAnnotation(data); - this.addAnnotation(annotation); - } - - // noinspection JSUnusedGlobalSymbols used by FeedbackCodeRenderer. - public addMachineAnnotations(annotations: MachineAnnotationData[]): void { - annotations.forEach(ma => this.addMachineAnnotation(ma)); - } - - // Unit-testing purposes. - private addUserAnnotation(data: UserAnnotationData): void { - const annotation = new UserAnnotation(data, - (a, cb) => this.createUpdateAnnotationForm(a, cb)); - this.addAnnotation(annotation); - } - - // Unit-testing purposes. - public addUserAnnotations(annotations: UserAnnotationData[]): void { - annotations.forEach(ua => this.addUserAnnotation(ua)); - } - - // noinspection JSUnusedGlobalSymbols used by FeedbackCodeRenderer. - public async loadUserAnnotations(): Promise { - return getAllUserAnnotations(this.submissionId, - (a, cb) => this.createUpdateAnnotationForm(a, cb)) - .then(annotations => { - annotations.forEach(annotation => this.addAnnotation(annotation)); - initTooltips(); - }); - } - - public removeAnnotation(annotation: Annotation): void { - const line = annotation.global ? 0 : annotation.line; - - // Remove the annotation from the map. - const lineAnnotations = this.annotations.get(line).filter(a => a.__id !== annotation.__id); - if (lineAnnotations.length === 0) { - this.annotations.delete(line); - - // Remove the padding from the annotation list. - if (annotation.global) { - this.globalAnnotations.classList.remove("has-annotations"); - } - } else { - this.annotations.set(line, lineAnnotations); - } - - this.updateViewState(); - } - - public updateAnnotation(original: Annotation, updated: Annotation): void { - const origLine = original.global ? 0 : original.line; - const updLine = updated.global ? 0 : updated.line; - - // Different line -> can simply remove and re-add. - if (origLine !== updLine) { - this.removeAnnotation(original); - this.addAnnotation(updated); - return; - } - - // Find the annotation in the map. - const lineAnnotations = this.annotations.get(origLine); - lineAnnotations.forEach((annotation, idx) => { - if (annotation.__id === original.__id) { - lineAnnotations[idx] = updated; - } - }); - this.annotations.set(updLine, lineAnnotations); - - // Replace the HTML component. - document.querySelector(`#annotation-div-${original.__id}`).replaceWith(updated.html); - } - - // ///////////////////////////////////////////////////////////////////////// - // DOM operations ////////////////////////////////////////////////////////// - // ///////////////////////////////////////////////////////////////////////// - - private appendAnnotationToGlobal(annotation: Annotation): void { - // Append the annotation. - this.globalAnnotationGroups - .querySelector(`.annotation-group-${GROUP_MAPPING[annotation.type]}`) - .appendChild(annotation.html); - - // Add the padding to the global annotation list. - this.globalAnnotations.classList.add("has-annotations"); - } - - private appendAnnotationToTable(annotation: Annotation): void { - const line = Math.min(annotation.line, this.codeLines); - const row = this.table.querySelector(`#line-${line}`); - - const cell = row.querySelector(`#annotation-cell-${line}`); - if (cell.querySelector(`.annotation-group-${GROUP_MAPPING[annotation.type]}`) === null) { - // Create the dot. - const dot = document.createElement("span") as HTMLSpanElement; - dot.classList.add("dot", "hide"); - dot.id = `dot-${line}`; - row.querySelector(".rouge-gutter").prepend(dot); - - // Create annotation groups. - GROUP_ORDER.forEach((type: string) => { - const group = document.createElement("div") as HTMLDivElement; - group.classList.add(`annotation-group-${type}`); - cell.appendChild(group); - }); - } - - // Append the annotation. - cell.querySelector(`.annotation-group-${GROUP_MAPPING[annotation.type]}`) - .appendChild(annotation.html); - } - - // ////////////////////////////////////////////////////////////////////////// - // Initialisations ////////////////////////////////////////////////////////// - // ////////////////////////////////////////////////////////////////////////// - - private initAnnotations(): void { - // Create global annotation groups. - GROUP_ORDER.forEach((type: string) => { - const group = document.createElement("div") as HTMLDivElement; - group.classList.add(`annotation-group-${type}`); - this.globalAnnotationGroups.appendChild(group); - }); - - // Create annotation cells. - for (let i = 1; i <= this.codeLines; ++i) { - const tableLine = this.table.querySelector(`#line-${i}`); - tableLine.dataset["line"] = i.toString(); - // Add an annotation container underneath the pre element. - const lineCodeCell = tableLine.querySelector(".rouge-code"); - const annotationContainer = document.createElement("div") as HTMLDivElement; - annotationContainer.classList.add("annotation-cell"); - annotationContainer.id = `annotation-cell-${i}`; - lineCodeCell.appendChild(annotationContainer); - } - - // Toggle buttons. - this.hideAllAnnotations.addEventListener("click", () => this.hideAnnotations()); - this.showAllAnnotations.addEventListener("click", () => this.showAnnotations()); - this.showErrorAnnotations.addEventListener("click", () => this.hideAnnotations(true)); - } - - // noinspection JSUnusedGlobalSymbols used by FeedbackCodeRenderer. - public initAnnotateButtons(): void { - // Global annotations. - const globalButton = document.querySelector(annotationGlobalAdd); - globalButton.addEventListener("click", () => this.handleAnnotateGlobal()); - - const type = this.questionMode ? "user_question" : "user_annotation"; - const title = I18n.t(`js.${type}.send`); - - // Inline annotations. - const codeLines = this.table.querySelectorAll(".lineno"); - codeLines.forEach((codeLine: HTMLTableRowElement) => { - const annotationButton = document.createElement("button") as HTMLButtonElement; - annotationButton.classList.add("btn", "btn-icon", "btn-icon-filled", "bg-primary", "annotation-button"); - annotationButton.addEventListener("click", () => this.handleAnnotateLine(codeLine)); - annotationButton.title = title; - - const annotationButtonIcon = document.createElement("i") as HTMLElement; - const clazz = this.questionMode ? "mdi-comment-question-outline" : "mdi-comment-plus-outline"; - annotationButtonIcon.classList.add("mdi", clazz, "mdi-18"); - annotationButton.appendChild(annotationButtonIcon); - - codeLine.querySelector(".rouge-gutter").prepend(annotationButton); - }); - } - - // ///////////////////////////////////////////////////////////////////////// - // Show and hide annotations /////////////////////////////////////////////// - // ///////////////////////////////////////////////////////////////////////// - - public hideAnnotations(keepImportant = false): void { - this.annotations.forEach((annotations, _line) => { - // Do not hide global annotations. - if (_line === 0) { - return; - } - const line = Math.min(_line, this.codeLines); - - // Find the dot for this line. - const dot = this.table.querySelector(`#dot-${line}`); - - // Determine the colours of the dot for this line. - const colours = annotations - .filter(annotation => !annotation.important || !keepImportant) - .map(annotation => `dot-${annotation.type}`); - - // Configure the dot. - if (colours.length > 0) { - // Remove previous colours. - dot.classList.remove("hide", ...GROUP_ORDER.map(type => `dot-${type}`)); - - // Add new colours. - dot.classList.add(...colours); - - // Help text. - const count = annotations.length; - if (count === 1) { - dot.title = I18n.t("js.annotation.hidden.single"); - } else { - dot.title = I18n.t("js.annotation.hidden.plural", { count: count }); - } - } else { - // Hide the dot. - dot.classList.add("hide"); - } - }); - - this.annotations.forEach(annotations => annotations.forEach(annotation => { - if (annotation.global || (keepImportant && annotation.important)) { - annotation.show(); - } else { - annotation.hide(); - } - })); - } - - public showAnnotations(): void { - this.annotations.forEach(annotations => { - annotations.forEach(annotation => annotation.show()); - }); - - // Hide all dots. - this.table.querySelectorAll(".dot").forEach(dot => { - dot.classList.add("hide"); - }); - } - - // ///////////////////////////////////////////////////////////////////////// - // Creating and modifying user annotations ///////////////////////////////// - // ///////////////////////////////////////////////////////////////////////// - - private createNewAnnotationForm(line: number | null): HTMLElement { - const annotationForm = new AnnotationForm(); - annotationForm.id = `annotation-submission-new-${line || 0}`; - annotationForm.questionMode = this.questionMode; - annotationForm.courseId = this.courseId; - annotationForm.exerciseId = this.exerciseId; - annotationForm.userId = this.userId; - - annotationForm.addEventListener("submit", async (e: CustomEvent) => { - const annotationData: UserAnnotationFormData = { - "annotation_text": e.detail.text, - "line_nr": (line === null ? null : line - 1), - "evaluation_id": this.evaluationId || undefined, - "saved_annotation_id": e.detail.savedAnnotationId || undefined, - }; - - try { - const mode = this.questionMode ? "question" : "annotation"; - const annotation = await createUserAnnotation(annotationData, this.submissionId, - (a, cb) => this.createUpdateAnnotationForm(a, cb), mode); - await this.createSavedAnnotation(annotation, e.detail); - this.addAnnotation(annotation); - invalidateSavedAnnotation(e.detail.savedAnnotationId); - annotationForm.remove(); - } catch (err) { - annotationForm.hasErrors = true; - annotationForm.disabled = false; - } - }); - - annotationForm.addEventListener("cancel", () => annotationForm.remove()); - - return annotationForm; - } - - private createUpdateAnnotationForm(annotation: UserAnnotation, - callback: CallableFunction): HTMLElement { - const annotationForm = new AnnotationForm(); - annotationForm.id = `annotation-submission-update-${annotation.id}`; - annotationForm.annotation = annotation; - annotationForm.questionMode = this.questionMode; - annotationForm.courseId = this.courseId; - annotationForm.exerciseId = this.exerciseId; - annotationForm.userId = this.userId; - - annotationForm.addEventListener("submit", async (e: CustomEvent) => { - const annotationData: UserAnnotationFormData = { - "annotation_text": e.detail.text, - "saved_annotation_id": e.detail.savedAnnotationId || undefined, - }; - - try { - const updated = await annotation.update(annotationData) as UserAnnotation; - await this.createSavedAnnotation(updated, e.detail); - this.updateAnnotation(annotation, updated); - if (e.detail.savedAnnotationId != annotation.savedAnnotationId ) { - invalidateSavedAnnotation(e.detail.savedAnnotationId); - invalidateSavedAnnotation(annotation.savedAnnotationId); - } - // Ask MathJax to search for math in the annotations - window.MathJax.typeset(); - } catch (err) { - annotationForm.hasErrors= true; - annotationForm.disabled= false; - } - }); - - annotationForm.addEventListener("cancel", () => callback()); - annotationForm.addEventListener("delete", () => annotation.remove().then(() => this.removeAnnotation(annotation))); - - return annotationForm; - } - - private async createSavedAnnotation(from: UserAnnotation, eventDetail: { savedAnnotationTitle: string, text: string, saveAnnotation: boolean }): Promise { - if (eventDetail.saveAnnotation) { - try { - from.savedAnnotationId = await createSavedAnnotation({ - from: from.id, - saved_annotation: { - title: eventDetail.savedAnnotationTitle, - annotation_text: eventDetail.text, - } - }); - } catch (errors) { - alert(I18n.t("js.saved_annotation.new.errors", { count: errors.length }) + "\n\n" + errors.join("\n")); - } - } - } - - private handleAnnotateGlobal(): void { - // Attempt to find an existing form and reuse that. - const formId = "#annotation-submission-0"; - let form = this.globalAnnotations.querySelector(formId); - if (form === null) { - form = this.createNewAnnotationForm(null); - - // Inject the form into the div. - this.globalAnnotations.prepend(form); - } - } - - private handleAnnotateLine(row: HTMLTableRowElement): void { - const lineNo = parseInt(row.dataset["line"]); - - // Attempt to find an existing form and reuse that. - let form = row.querySelector(`#annotation-submission-new-${lineNo}`); - if (form === null) { - form = this.createNewAnnotationForm(lineNo); - // Inject the form into the table. - const cell = row.querySelector(`#annotation-cell-${lineNo}`); - cell.prepend(form); - } - } - - // ///////////////////////////////////////////////////////////////////////// - // Update view ///////////////////////////////////////////////////////////// - // ///////////////////////////////////////////////////////////////////////// - - private get annotationCount(): number { - return Array.from(this.annotations.values()) - .map(ann => ann.length) - .reduce((acc, nw) => acc + nw, 0); - } - - /** - * Updates the badge count and button visibility state. - */ - private updateViewState(): void { - const amount = this.annotationCount; - if (amount > 0) { - // Set the badge count. - this.badge.innerText = amount.toString(); - - // Find the important annotations. - const importantAnnotationCount = Array.from(this.annotations.values()) - .map(annotations => annotations.filter(an => an.important).length) - .reduce((acc, nw) => acc + nw); - if (importantAnnotationCount === 0 || importantAnnotationCount === amount) { - this.showErrorAnnotations.classList.add("hide"); - } else { - this.showErrorAnnotations.classList.remove("hide"); - } - - // Show the annotation toggles. - this.annotationToggles.classList.remove("hide"); - - // Ask MathJax to search for math in the annotations - window.MathJax.typeset(); - } else { - // No annotations have been added (yet). - this.badge.innerText = ""; - - // Hide the annotation toggles. - this.annotationToggles.classList.add("hide"); - } - } -} diff --git a/app/assets/javascripts/code_listing/machine_annotation.ts b/app/assets/javascripts/code_listing/machine_annotation.ts deleted file mode 100644 index b183830368..0000000000 --- a/app/assets/javascripts/code_listing/machine_annotation.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Annotation, AnnotationType } from "code_listing/annotation"; - -export interface MachineAnnotationData { - type: AnnotationType; - text: string; - row: number; - externalUrl: string | null; -} - -export class MachineAnnotation extends Annotation { - private readonly externalUrl: string | null; - - constructor(data: MachineAnnotationData) { - // Filter out lines only containing dashes. - let text = data.text.split("\n") - .filter(s => !s.match("^--*$")) - .join("\n"); - // use the dom engine to encode the text to html - const node = document.createElement("span"); - node.textContent = text; - text = node.innerHTML; - super(data.row + 1, text, data.type); - this.externalUrl = data.externalUrl; - } - - protected get class(): string { - return "machine-annotation"; - } - - protected get meta(): string { - return this.title; - } - - protected get title(): string { - return I18n.t(`js.annotation.type.${this.type}`); - } - - protected get hasNotice(): boolean { - return this.externalUrl !== null && this.externalUrl !== undefined; - } - - protected get noticeUrl(): string | null { - return this.externalUrl; - } - - protected get noticeInfo(): string | null { - return I18n.t("js.machine_annotation.external_url"); - } - - protected get useNoticeIcon(): boolean { - return false; - } -} diff --git a/app/assets/javascripts/code_listing/question_annotation.ts b/app/assets/javascripts/code_listing/question_annotation.ts deleted file mode 100644 index e487e71b7f..0000000000 --- a/app/assets/javascripts/code_listing/question_annotation.ts +++ /dev/null @@ -1,171 +0,0 @@ -import { Annotation, QuestionState } from "code_listing/annotation"; -import { fetch } from "util.js"; -import { - UserAnnotation, - UserAnnotationData, - UserAnnotationEditor, UserAnnotationFormData, - UserAnnotationPermissionData -} from "code_listing/user_annotation"; -import { Notification } from "notification"; - -interface QuestionAnnotationPermissionData extends UserAnnotationPermissionData { - transition: {[state in QuestionState]: boolean}; -} - -export interface QuestionAnnotationData extends UserAnnotationData { - // eslint-disable-next-line camelcase - question_state: QuestionState; - // eslint-disable-next-line camelcase - newer_submission_url: string | null; -} - -export class QuestionAnnotation extends UserAnnotation { - public readonly permissions: QuestionAnnotationPermissionData; - private readonly questionState: QuestionState; - private readonly newerSubmissionUrl: string | null; - - constructor(data: QuestionAnnotationData, editFn: UserAnnotationEditor) { - super(data, editFn, "question"); - this.questionState = data.question_state; - this.newerSubmissionUrl = data.newer_submission_url; - this.permissions = data.permission as QuestionAnnotationPermissionData; - } - - protected get meta(): string { - const timestamp = I18n.formatDate(this.createdAt, "time.formats.annotation"); - const user = this.user.name; - const questionState = I18n.t(`js.question.state.${this.questionState}`); - - if (this.questionState === "unanswered") { - return I18n.t("js.user_question.meta_unanswered", { - user: user, - time: timestamp, - state: questionState - }); - } else { - return I18n.t("js.user_question.meta_else", { - user: user, - time: timestamp, - state: questionState, - last: this.lastUpdatedBy.name - }); - } - } - - protected get hasNotice(): boolean { - return this.newerSubmissionUrl !== null; - } - - protected get noticeUrl(): string | null { - return this.newerSubmissionUrl; - } - - protected get noticeInfo(): string | null { - return I18n.t("js.user_question.has_newer_submission"); - } - - public transitionable(to: QuestionState): boolean { - return this.permissions.transition[to]; - } - - protected get editTitle(): string { - return I18n.t("js.user_question.edit"); - } - - public async update(formData: UserAnnotationFormData): Promise { - const response = await fetch(this.url, { - headers: { "Content-Type": "application/json" }, - method: "PATCH", - body: JSON.stringify({ - question: formData - }) - }); - const data = await response.json(); - - if (response.ok) { - return new QuestionAnnotation(data, this.editor); - } - throw new Error(); - } - - protected async transition(newState: QuestionState): Promise { - fetch(this.url, { - method: "PATCH", - headers: { - "Accept": "application/json", - "Content-Type": "application/json" - }, - body: JSON.stringify({ - from: this.questionState, - question: { - // eslint-disable-next-line camelcase - question_state: newState - } - }) - }).then(async response => { - if (response.ok) { - const json = await response.json(); - const newAnnotation: Annotation = new QuestionAnnotation(json, this.editor); - window.dodona.codeListing.updateAnnotation(this, newAnnotation); - } else if (response.status === 404) { - // Someone already deleted this question. - new dodona.Toast(I18n.t("js.user_question.deleted")); - window.dodona.codeListing.removeAnnotation(this); - this.__html.remove(); - } else if (response.status == 403) { - // Someone already changed the status of this question. - new dodona.Toast(I18n.t("js.user_question.conflict")); - // We now need to update the annotation, but we don't have the new data. - // Get the annotation from the backend. - this.selfUpdate(); - } - }); - } - - private selfUpdate(): void { - fetch(`/annotations/${this.id}`, { - headers: { - "Accept": "application/json" - } - }) - .then(r => r.json()) - .then(r => { - const newAnnotation: Annotation = new QuestionAnnotation(r, this.editor); - window.dodona.codeListing.updateAnnotation(this, newAnnotation); - }); - } -} - -function annotationFromData(data: UserAnnotationData, - editFn: UserAnnotationEditor): UserAnnotation { - if (data.type == "question") { - return new QuestionAnnotation(data as QuestionAnnotationData, editFn); - } - return new UserAnnotation(data, editFn); -} - -export async function createUserAnnotation(formData: UserAnnotationFormData, - submissionId: number, - editFn: UserAnnotationEditor, mode = "annotation"): Promise { - const response = await fetch(`/submissions/${submissionId}/annotations.json`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ [mode]: formData }) - }); - const data = await response.json(); - - if (response.ok) { - if (mode === "question") { - Notification.startNotificationRefresh(); - } - return annotationFromData(data, editFn); - } - throw new Error(); -} - -export async function getAllUserAnnotations(submission: number, - editFn: UserAnnotationEditor): Promise { - const response = await fetch(`/submissions/${submission}/annotations.json`); - const json = await response.json(); - return json.map(data => annotationFromData(data, editFn)); -} diff --git a/app/assets/javascripts/code_listing/user_annotation.ts b/app/assets/javascripts/code_listing/user_annotation.ts deleted file mode 100644 index c5095f663e..0000000000 --- a/app/assets/javascripts/code_listing/user_annotation.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { Annotation, AnnotationType } from "code_listing/annotation"; -import { fetch } from "util.js"; - -export interface UserAnnotationFormData { - // eslint-disable-next-line camelcase - annotation_text: string; - // eslint-disable-next-line camelcase - line_nr?: number | null; - // eslint-disable-next-line camelcase - evaluation_id?: number | undefined; - // eslint-disable-next-line camelcase - saved_annotation_id?: string; -} - -export type UserAnnotationEditor = (ua: UserAnnotation, cb: CallableFunction) => HTMLElement; - -interface UserAnnotationUserData { - name: string; -} - -export interface UserAnnotationPermissionData { - update: boolean; - destroy: boolean; - can_see_annotator: boolean -} - -export interface UserAnnotationData { - // eslint-disable-next-line camelcase - annotation_text: string; - // eslint-disable-next-line camelcase - created_at: string; - id: number; - // eslint-disable-next-line camelcase - line_nr: number; - permission: UserAnnotationPermissionData; - released: boolean; - // eslint-disable-next-line camelcase - rendered_markdown: string; - // eslint-disable-next-line camelcase - evaluation_id: number | null; - // eslint-disable-next-line camelcase - saved_annotation_id: number | null; - url: string; - user: UserAnnotationUserData; - type: string; - // eslint-disable-next-line camelcase - last_updated_by: UserAnnotationUserData; - // REMOVE AFTER CLOSED BETA - // eslint-disable-next-line camelcase - course_id: number; -} - -export class UserAnnotation extends Annotation { - protected readonly editor: UserAnnotationEditor; - - public readonly createdAt: string; - public readonly id: number; - public readonly permissions: UserAnnotationPermissionData; - private readonly __rawText: string; - public readonly released: boolean; - public readonly evaluationId: number | null; - private __savedAnnotationId: number | null; - public readonly url: string; - public readonly user: UserAnnotationUserData | undefined; - public readonly lastUpdatedBy: UserAnnotationUserData | undefined; - // REMOVE AFTER CLOSED BETA - private readonly __courseId: number; - - constructor(data: UserAnnotationData, - editFn: UserAnnotationEditor, type: AnnotationType = "user") { - const line = data.line_nr === null ? null : data.line_nr + 1; - super(line, data.rendered_markdown, type); - this.createdAt = data.created_at; - this.editor = editFn; - this.id = data.id; - this.permissions = data.permission; - this.released = data.released; - this.__rawText = data.annotation_text; - this.evaluationId = data.evaluation_id; - this.__savedAnnotationId = data.saved_annotation_id; - this.url = data.url; - this.user = data.user; - this.lastUpdatedBy = data.last_updated_by; - // REMOVE AFTER CLOSED BETA - this.__courseId = data.course_id; - } - - protected edit(): void { - const editButton = this.__html.querySelector(".annotation-edit"); - - const editor = this.editor(this, () => { - const editFormId = `#annotation-submission-update-${this.id}`; - this.__html.querySelector(editFormId).replaceWith(this.body); - editButton.classList.remove("hide"); - }); - editButton.classList.add("hide"); - - this.__html.querySelector(".annotation-text").replaceWith(editor); - } - - protected get meta(): string { - if (!this.permissions.can_see_annotator) { - return I18n.t("js.user_annotation.anonymous_message"); - } - - const timestamp = I18n.formatDate(this.createdAt, "time.formats.annotation"); - const user = this.user!.name; - - return I18n.t("js.user_annotation.meta", { user: user, time: timestamp }); - } - - public get modifiable(): boolean { - return this.permissions.update; - } - - public get rawText(): string { - return this.__rawText; - } - - public get savedAnnotationId(): number | null { - return this.__savedAnnotationId; - } - - public set savedAnnotationId(sa: number | null) { - this.__savedAnnotationId = sa; - } - - public get removable(): boolean { - return this.permissions.destroy; - } - - public get visible(): boolean { - return this.released; - } - - public async remove(): Promise { - return fetch(this.url, { method: "DELETE" }).then(() => { - super.remove(); - }); - } - - protected get title(): string { - return I18n.t(`js.annotation.type.${this.type}`); - } - - public async update(formData: UserAnnotationFormData): Promise { - const response = await fetch(this.url, { - headers: { "Content-Type": "application/json" }, - method: "PATCH", - body: JSON.stringify({ - annotation: formData - }) - }); - const data = await response.json(); - - if (response.ok) { - return new UserAnnotation(data, this.editor); - } - throw new Error(); - } - - protected get editTitle(): string { - return I18n.t("js.user_annotation.edit"); - } - - // REMOVE AFTER CLOSED BETA - protected get courseId(): number { - return this.__courseId; - } -} diff --git a/app/assets/javascripts/components/annotations/annotation_form.ts b/app/assets/javascripts/components/annotations/annotation_form.ts index b9669028fb..9c22e808c8 100644 --- a/app/assets/javascripts/components/annotations/annotation_form.ts +++ b/app/assets/javascripts/components/annotations/annotation_form.ts @@ -1,12 +1,12 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; -import { Annotation } from "code_listing/annotation"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import { isBetaCourse } from "saved_annotation_beta"; -import { watchMixin } from "components/watch_mixin"; +import { watchMixin } from "components/meta/watch_mixin"; import { createRef, Ref, ref } from "lit/directives/ref.js"; import "components/saved_annotations/saved_annotation_input"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import { annotationState } from "state/Annotations"; // Min and max of the annotation text is defined in the annotation model. const maxLength = 10_000; @@ -16,39 +16,32 @@ const maxLength = 10_000; * * @element d-annotation-form * - * @prop {Annotation} annotation - the annotation that will be edited (Null for a creation form) - * @prop {Number} courseId - used to fetch saved annotations by course - * @prop {Number} exerciseId - used to fetch saved annotations by exercise - * @prop {Number} userId - used to fetch saved annotations by user - * @prop {Boolean} questionMode - whether we are editing questions or userAnnotations + * @prop {String} annotationText - the text of the annotation + * @prop {String} savedAnnotationId - the id of the saved annotation * @prop {Boolean} disabled - disables all buttons * @prop {Boolean} hasErrors - Shows red validation styling + * @prop {String} submitButtonText - the I18n key of the text for the submit button * * @fires cancel - if a users uses the cancel button - * @fires delete - if the user confirms after pressing the delete button * @fires submit - if the users presses the submit button, detail contains {text: string, savedAnnotationId: string} */ @customElement("d-annotation-form") export class AnnotationForm extends watchMixin(ShadowlessLitElement) { - @property({ type: Object }) - annotation: Annotation; - @property({ type: Boolean }) - questionMode: boolean; - @property({ type: Number, attribute: "course-id" }) - courseId: number; - @property({ type: Number, attribute: "exercise-id" }) - exerciseId: number; - @property({ type: Number, attribute: "user-id" }) - userId: number; + @property({ type: String, attribute: "annotation-text" }) + annotationText: string; + @property({ type: String, attribute: "saved-annotation-id" }) + savedAnnotationId: string; @property({ type: Boolean }) disabled = false; @property({ type: Boolean, attribute: "has-errors" }) hasErrors = false; + @property({ type: String, attribute: "submit-button-text" }) + submitButtonText: string; @property({ state: true }) - annotationText = ""; + _annotationText = ""; @property({ state: true }) - savedAnnotationId = ""; + _savedAnnotationId = ""; @property({ state: true }) savedAnnotationTitle: string; @property({ state: true }) @@ -58,29 +51,31 @@ export class AnnotationForm extends watchMixin(ShadowlessLitElement) { titleRef: Ref = createRef(); watch = { - annotation: () => { - this.annotationText = this.annotation?.rawText; - this.savedAnnotationId = this.annotation?.savedAnnotationId?.toString() || ""; + annotationText: () => { + this._annotationText = this.annotationText; + }, + savedAnnotationId: () => { + this._savedAnnotationId = this.savedAnnotationId || ""; } }; get type(): string { - return this.questionMode ? "user_question" : "user_annotation"; + return annotationState.isQuestionMode ? "user_question" : "user_annotation"; } get rows(): number { - return Math.max(3, this.annotationText.split("\n").length + 1); + return Math.max(3, this._annotationText.split("\n").length + 1); } handleSavedAnnotationInput(e: CustomEvent): void { if (e.detail.text) { - this.annotationText = e.detail.text; + this._annotationText = e.detail.text; } - this.savedAnnotationId = e.detail.id; + this._savedAnnotationId = e.detail.id; } handleTextInput(): void { - this.annotationText = this.inputRef.value.value; + this._annotationText = this.inputRef.value.value; } handleCancel(): void { @@ -88,14 +83,6 @@ export class AnnotationForm extends watchMixin(ShadowlessLitElement) { this.dispatchEvent(event); } - handleDelete(): void { - const confirmText = I18n.t(`js.${this.type}.delete_confirm`); - if (confirm(confirmText)) { - const event = new CustomEvent("delete", { bubbles: true, composed: true }); - this.dispatchEvent(event); - } - } - handleSubmit(): void { if (!this.disabled) { this.hasErrors = false; @@ -109,8 +96,8 @@ export class AnnotationForm extends watchMixin(ShadowlessLitElement) { const event = new CustomEvent("submit", { detail: { - text: this.annotationText, - savedAnnotationId: this.savedAnnotationId, + text: this._annotationText, + savedAnnotationId: this._savedAnnotationId, savedAnnotationTitle: this.savedAnnotationTitle, saveAnnotation: this.saveAnnotation, }, @@ -147,29 +134,28 @@ export class AnnotationForm extends watchMixin(ShadowlessLitElement) { this.saveAnnotation = !this.saveAnnotation; if (this.saveAnnotation && !this.savedAnnotationTitle) { // Take the first five words, with a max of 40 chars as default title - this.savedAnnotationTitle = this.annotationText.split(/\s+/).slice(0, 5).join(" ").slice(0, 40); + this.savedAnnotationTitle = this._annotationText.split(/\s+/).slice(0, 5).join(" ").slice(0, 40); } } render(): TemplateResult { - const form = html` + return html`
- ${this.questionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse(this.courseId) ? "" : html` + ${annotationState.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse() ? "" : html` `}
- + ${annotationState.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse() ? "" : html` + + `}
${unsafeHTML(I18n.t("js.user_annotation.help"))} - ${this.questionMode ? html` + ${annotationState.isQuestionMode ? html` ${unsafeHTML(I18n.t("js.user_annotation.help_student"))} ` : ""} - ${I18n.formatNumber(this.annotationText.length)} / ${I18n.formatNumber(maxLength)} + ${I18n.formatNumber(this._annotationText.length)} / ${I18n.formatNumber(maxLength)}
- ${this.questionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse(this.courseId) ? "" : html` + ${annotationState.isQuestionMode || /* REMOVE AFTER CLOSED BETA */ !isBetaCourse() ? "" : html`
@@ -219,15 +205,6 @@ export class AnnotationForm extends watchMixin(ShadowlessLitElement) { ` : html``} `}
- ${this.annotation && this.annotation.removable ? html` - - ` : ""}
`; - - return this.annotation !== undefined ? form : html` -
- ${form} -
- `; } } diff --git a/app/assets/javascripts/components/annotations/annotation_marker.ts b/app/assets/javascripts/components/annotations/annotation_marker.ts new file mode 100644 index 0000000000..c35321797d --- /dev/null +++ b/app/assets/javascripts/components/annotations/annotation_marker.ts @@ -0,0 +1,132 @@ +import { customElement, property } from "lit/decorators.js"; +import { render, html, LitElement, TemplateResult } from "lit"; +import tippy, { Instance as Tippy, createSingleton } from "tippy.js"; +import { AnnotationData, annotationState, compareAnnotationOrders, isUserAnnotation } from "state/Annotations"; +import { StateController } from "state/state_system/StateController"; + +/** + * A marker that styles the slotted content and shows a tooltip with annotations. + * + * @prop {AnnotationData[]} annotations The annotations to show in the tooltip. + * + * @element d-annotation-marker + */ +@customElement("d-annotation-marker") +export class AnnotationMarker extends LitElement { + @property({ type: Array }) + annotations: AnnotationData[]; + + state = new StateController(this); + + + static colors = { + "error": "var(--error-color, red)", + "warning": "var(--warning-color, yellow)", + "info": "var(--info-color, blue)", + "annotation": "var(--annotation-color, green)", + "question": "var(--question-color, orange)", + "annotation-intense": "var(--annotation-intense-color, green)", + "question-intense": "var(--question-intense-color, orange)", + }; + + static getStyle(annotation: AnnotationData): string { + if (["error", "warning", "info"].includes(annotation.type)) { + return `text-decoration: wavy underline ${AnnotationMarker.colors[annotation.type]} ${annotationState.isHovered(annotation) ? 2 : 1}px;`; + } else { + const colorKey = annotationState.isHovered(annotation) ? `${annotation.type}-intense` : annotation.type; + return ` + background: ${AnnotationMarker.colors[colorKey]}; + padding-top: 2px; + padding-bottom: 2px; + margin-top: -2px; + margin-bottom: -2px; + `; + } + } + + static tippyInstances: Tippy[] = []; + // Using a singleton to avoid multiple tooltips being open at the same time. + static tippySingleton = createSingleton([], { + placement: "bottom-start", + interactive: true, + interactiveDebounce: 25, + delay: [500, 25], + offset: [-10, -2], + // This transition fixes a bug where overlap with the previous tooltip was taken into account when positioning + moveTransition: "transform 0.001s ease-out", + appendTo: () => document.querySelector(".code-table"), + }); + static registerTippyInstance(instance: Tippy): void { + this.tippyInstances.push(instance); + this.tippySingleton.setInstances(this.tippyInstances); + } + static unregisterTippyInstance(instance: Tippy): void { + this.tippyInstances = this.tippyInstances.filter(i => i !== instance); + this.tippySingleton.setInstances(this.tippyInstances); + } + + // Annotations that are displayed inline should show up as tooltips. + get hiddenAnnotations(): AnnotationData[] { + return this.annotations.filter(a => !annotationState.isVisible(a)).sort(compareAnnotationOrders); + } + + tippyInstance: Tippy; + + renderTooltip(): void { + if (this.tippyInstance) { + AnnotationMarker.unregisterTippyInstance(this.tippyInstance); + this.tippyInstance.destroy(); + this.tippyInstance = undefined; + } + + if (this.hiddenAnnotations.length === 0) { + return; + } + + const tooltip = document.createElement("div"); + tooltip.classList.add("marker-tooltip"); + render(this.hiddenAnnotations.map(a => isUserAnnotation(a) ? + html`` : + html``), tooltip); + + this.tippyInstance = tippy(this, { + content: tooltip, + }); + AnnotationMarker.registerTippyInstance(this.tippyInstance); + } + + get sortedAnnotations(): AnnotationData[] { + return this.annotations.sort( (a, b) => { + if (annotationState.isHovered(a)) { + return -1; + } else if (annotationState.isHovered(b)) { + return 1; + } else { + return compareAnnotationOrders(a, b); + } + }); + } + + get machineAnnotationMarkerSVG(): TemplateResult | undefined { + const firstMachineAnnotation = this.sortedAnnotations.find(a => !isUserAnnotation(a)); + const size = annotationState.isHovered(firstMachineAnnotation) ? 20 : 14; + return firstMachineAnnotation && html` + + `; + } + + get annotationStyles(): string { + return this.sortedAnnotations.reverse().map(a => AnnotationMarker.getStyle(a)).join(" "); + } + + render(): TemplateResult { + this.renderTooltip(); + + return html`${this.machineAnnotationMarkerSVG}`; + } +} diff --git a/app/assets/javascripts/components/annotations/annotation_options.ts b/app/assets/javascripts/components/annotations/annotation_options.ts new file mode 100644 index 0000000000..ba5706a823 --- /dev/null +++ b/app/assets/javascripts/components/annotations/annotation_options.ts @@ -0,0 +1,52 @@ +import { customElement, property } from "lit/decorators.js"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { html, TemplateResult } from "lit"; +import "components/annotations/annotations_toggles"; +import "components/annotations/hidden_annotations_dot"; +import { i18nMixin } from "components/meta/i18n_mixin"; +import { userState } from "state/Users"; +import { annotationState } from "state/Annotations"; + + +/** + * This component represents the top row with options about annotations, that is displayed above the code listing. + * It contains the button to add a new global annotation, the global annotations themselves and the toggles to show/hide annotations. + * + * @element d-annotation-options + */ +@customElement("d-annotation-options") +export class AnnotationOptions extends i18nMixin(ShadowlessLitElement) { + @property({ state: true }) + showForm = false; + + get canCreateAnnotation(): boolean { + return userState.hasPermission("annotation.create"); + } + + get addAnnotationTitle(): string { + return annotationState.isQuestionMode ? + I18n.t("js.annotations.options.add_global_question") : + I18n.t("js.annotations.options.add_global_annotation"); + } + + protected render(): TemplateResult { + return html` + +
+ this.showForm = false} + > +
+ `; + } +} diff --git a/app/assets/javascripts/components/annotations/annotations_cell.ts b/app/assets/javascripts/components/annotations/annotations_cell.ts new file mode 100644 index 0000000000..38ab33301e --- /dev/null +++ b/app/assets/javascripts/components/annotations/annotations_cell.ts @@ -0,0 +1,98 @@ +import { customElement, property } from "lit/decorators.js"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { html, TemplateResult } from "lit"; +import { UserAnnotationFormData, userAnnotationState } from "state/UserAnnotations"; +import { annotationState, compareAnnotationOrders } from "state/Annotations"; +import { submissionState } from "state/Submissions"; +import { MachineAnnotationData, machineAnnotationState } from "state/MachineAnnotations"; +import "components/annotations/machine_annotation"; +import "components/annotations/user_annotation"; +import "components/annotations/annotation_form"; +import "components/annotations/thread"; +import { AnnotationForm } from "components/annotations/annotation_form"; +import { createRef, Ref, ref } from "lit/directives/ref.js"; +import { evaluationState } from "state/Evaluations"; + +/** + * This component represents a cell that groups all annotations for a specific line. + * It also contains the form for creating new annotations. + * + * @element d-annotations-cell + * + * @prop {Number} row - the line number + * @prop {Boolean} showForm - if the form should be shown + * + * @fires close-form - if the form should be closed + */ +@customElement("d-annotations-cell") +export class AnnotationsCell extends ShadowlessLitElement { + @property({ type: Boolean, attribute: "show-form" }) + showForm: boolean; + @property({ type: Number }) + row: number; + + annotationFormRef: Ref = createRef(); + + get machineAnnotations(): MachineAnnotationData[] { + return machineAnnotationState.byLine.get(this.row) || []; + } + + get userAnnotationIds(): number[] { + return userAnnotationState.rootIdsByLine.get(this.row) || []; + } + + + async createAnnotation(e: CustomEvent): Promise { + const annotationData: UserAnnotationFormData = { + "annotation_text": e.detail.text, + "line_nr": this.row, + "evaluation_id": evaluationState.id, + "saved_annotation_id": e.detail.savedAnnotationId || undefined, + }; + + if (this.row > 0 && userAnnotationState.selectedRange) { + annotationData["line_nr"] = userAnnotationState.selectedRange.row; + annotationData["rows"] = userAnnotationState.selectedRange.rows; + annotationData["column"] = userAnnotationState.selectedRange.column; + annotationData["columns"] = userAnnotationState.selectedRange.columns; + } + + try { + const mode = annotationState.isQuestionMode ? "question" : "annotation"; + await userAnnotationState.create(annotationData, submissionState.id, mode, e.detail.saveAnnotation, e.detail.savedAnnotationTitle); + this.closeForm(); + } catch (err) { + this.annotationFormRef.value.hasErrors = true; + this.annotationFormRef.value.disabled = false; + } + } + + closeForm(): void { + const event = new CustomEvent("close-form", { bubbles: true, composed: true }); + this.dispatchEvent(event); + } + + protected render(): TemplateResult { + return html` +
+ ${this.showForm ? html` +
+ this.createAnnotation(e)} + @cancel=${() => this.closeForm()} + ${ref(this.annotationFormRef)} + submit-button-text="send" + > +
+ ` : ""} + ${this.userAnnotationIds.map(a => html` + + `)} + ${this.machineAnnotations + .filter(a => annotationState.isVisible(a)) + .sort(compareAnnotationOrders).map(a => html` + + `)} +
+ `; + } +} diff --git a/app/assets/javascripts/components/annotations/annotations_count_badge.ts b/app/assets/javascripts/components/annotations/annotations_count_badge.ts new file mode 100644 index 0000000000..90ad7f111f --- /dev/null +++ b/app/assets/javascripts/components/annotations/annotations_count_badge.ts @@ -0,0 +1,23 @@ +import { customElement } from "lit/decorators.js"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { userAnnotationState } from "state/UserAnnotations"; +import { html, TemplateResult } from "lit"; +import { machineAnnotationState } from "state/MachineAnnotations"; + +/** + * This component represents a badge that shows the total number of annotations. + * + * @element d-annotations-count-badge + */ +@customElement("d-annotations-count-badge") +export class AnnotationsCountBadge extends ShadowlessLitElement { + get annotationsCount(): number { + return userAnnotationState.count + machineAnnotationState.count; + } + + render(): TemplateResult { + return this.annotationsCount ? html` +
${this.annotationsCount}
+ ` : html``; + } +} diff --git a/app/assets/javascripts/components/annotations/annotations_toggles.ts b/app/assets/javascripts/components/annotations/annotations_toggles.ts new file mode 100644 index 0000000000..6e3bdb14e2 --- /dev/null +++ b/app/assets/javascripts/components/annotations/annotations_toggles.ts @@ -0,0 +1,55 @@ +import { customElement } from "lit/decorators.js"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { html, TemplateResult, PropertyValues } from "lit"; +import { annotationState } from "state/Annotations"; +import { i18nMixin } from "components/meta/i18n_mixin"; +import { initTooltips } from "util.js"; + +/** + * This component represents the toggles to show/hide annotations. + * It contains the buttons to show all annotations, only important annotations or no annotations. + * + * @element d-annotations-toggles + */ +@customElement("d-annotations-toggles") +export class AnnotationsToggles extends i18nMixin(ShadowlessLitElement) { + protected update(changedProperties: PropertyValues): void { + super.update(changedProperties); + initTooltips(this); + } + + protected render(): TemplateResult { + return html` + + ${I18n.t("js.annotations.toggles.title")} +
+ + + +
+
+ `; + } +} diff --git a/app/assets/javascripts/components/annotations/code_listing_row.ts b/app/assets/javascripts/components/annotations/code_listing_row.ts new file mode 100644 index 0000000000..78d3ad8c87 --- /dev/null +++ b/app/assets/javascripts/components/annotations/code_listing_row.ts @@ -0,0 +1,164 @@ +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { customElement, property } from "lit/decorators.js"; +import { html, TemplateResult } from "lit"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import "components/annotations/annotations_cell"; +import "components/annotations/annotation_marker"; +import "components/annotations/hidden_annotations_dot"; +import { i18nMixin } from "components/meta/i18n_mixin"; +import { initTooltips } from "util.js"; +import { PropertyValues } from "@lit/reactive-element"; +import { userState } from "state/Users"; +import { AnnotationData, annotationState, compareAnnotationOrders } from "state/Annotations"; +import { MachineAnnotationData, machineAnnotationState } from "state/MachineAnnotations"; +import { wrapRangesInHtml, range } from "mark"; +import { SelectedRange, UserAnnotationData, userAnnotationState } from "state/UserAnnotations"; +import { AnnotationMarker } from "components/annotations/annotation_marker"; +import "components/annotations/selection_marker"; +import "components/annotations/create_annotation_button"; +import { triggerSelectionStart } from "components/annotations/select"; + +/** + * This component represents a row in the code listing. + * It contains the line number and the code itself, and the button to add a new annotation for this row. + * It also contains the annotations for this row. + * + * @element d-code-listing-row + * + * @prop {number} row - The row number. + * @prop {string} renderedCode - The code to display. + */ +@customElement("d-code-listing-row") +export class CodeListingRow extends i18nMixin(ShadowlessLitElement) { + @property({ type: Number }) + row: number; + @property({ type: String, attribute: "rendered-code" }) + renderedCode: string; + + + /** + * Calculates the range of the code that is covered by the given annotation. + * If the annotation spans multiple lines, the range will be the whole line unless this is the first or last line. + * In that case, the range will be the part of the line that is covered by the annotation. + * @param annotation The annotation to calculate the range for. + */ + getRangeFromAnnotation(annotation: AnnotationData | SelectedRange): range { + const isMachineAnnotation = ["error", "warning", "info"].includes((annotation as AnnotationData).type); + const rowsLength = annotation.rows ?? 1; + let lastRow = annotation.row ? annotation.row + rowsLength : 0; + let firstRow = annotation.row ? annotation.row + 1 : 0; + + if (!isMachineAnnotation) { + // rows on user annotations are 1-based, so we need to subtract 1 + firstRow -= 1; + lastRow -= 1; + } + + let start = 0; + if (this.row === firstRow) { + start = annotation.column || 0; + } + + let length = Infinity; + if (this.row === lastRow) { + if (annotation.column !== undefined && annotation.column !== null) { + const defaultLength = isMachineAnnotation ? 0 : Infinity; + length = annotation.columns || defaultLength; + } + } + + return { start: start, length: length, data: annotation }; + } + + get wrappedCode(): string { + const annotationsToMark = [...this.userAnnotationsToMark, ...this.machineAnnotationsToMark].sort(compareAnnotationOrders); + const codeToMark = this.renderedCode; + let annotationsMarked = wrapRangesInHtml( + codeToMark, + annotationsToMark.map(a => this.getRangeFromAnnotation(a)), + "d-annotation-marker", + (node: AnnotationMarker, range) => { + // these nodes will be recompiled to html, so we need to store the data in a json string + const annotations = JSON.parse(node.getAttribute("annotations")) || []; + annotations.push(range.data); + node.setAttribute("annotations", JSON.stringify(annotations)); + }); + if ( userAnnotationState.showForm && this.shouldMarkSelection ) { + annotationsMarked = wrapRangesInHtml(annotationsMarked, [this.getRangeFromAnnotation(userAnnotationState.selectedRange)], "d-selection-marker"); + } + return annotationsMarked; + } + + firstUpdated(_changedProperties: PropertyValues): void { + super.firstUpdated(_changedProperties); + initTooltips(this); + this.addEventListener("pointerdown", e => triggerSelectionStart(e)); + } + + get canCreateAnnotation(): boolean { + return userState.hasPermission("annotation.create"); + } + + get machineAnnotationsToMark(): MachineAnnotationData[] { + return machineAnnotationState.byMarkedLine.get(this.row) || []; + } + + get userAnnotationsToMark(): UserAnnotationData[] { + return userAnnotationState.rootIdsByMarkedLine.get(this.row)?.map(i => userAnnotationState.byId.get(i)) || []; + } + + get shouldMarkSelection(): boolean { + return userAnnotationState.selectedRange && + userAnnotationState.selectedRange.row <= this.row && + userAnnotationState.selectedRange.row + (userAnnotationState.selectedRange.rows ?? 1) > this.row; + } + + get showForm(): boolean { + const range = userAnnotationState.selectedRange; + return userAnnotationState.showForm && range && range.row + range.rows - 1 === this.row; + } + + closeForm(): void { + userAnnotationState.showForm = false; + userAnnotationState.selectedRange = undefined; + } + + get fullLineAnnotations(): UserAnnotationData[] { + return this.userAnnotationsToMark + .filter(a => !a.column&& !a.columns) + .sort(compareAnnotationOrders); + } + + get hasFullLineSelection(): boolean { + return this.shouldMarkSelection && !userAnnotationState.selectedRange.column && !userAnnotationState.selectedRange.columns; + } + + get codeLineClass(): string { + return this.hasFullLineSelection ? `code-line-${annotationState.isQuestionMode ? "question" : "annotation"}` : ""; + } + + render(): TemplateResult { + return html` + + + ${this.canCreateAnnotation ? html`` : html``} + +
${this.row}
+ + + ${this.fullLineAnnotations.length > 0 ? html` + +
${unsafeHTML(this.wrappedCode)}
+
+ ` : html` +
${unsafeHTML(this.wrappedCode)}
+ `} + this.closeForm()} + > + + + `; + } +} diff --git a/app/assets/javascripts/components/annotations/create_annotation_button.ts b/app/assets/javascripts/components/annotations/create_annotation_button.ts new file mode 100644 index 0000000000..58cd26360a --- /dev/null +++ b/app/assets/javascripts/components/annotations/create_annotation_button.ts @@ -0,0 +1,74 @@ +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { customElement, property } from "lit/decorators.js"; +import { html, PropertyValues, TemplateResult } from "lit"; +import { annotationState } from "state/Annotations"; +import { userAnnotationState } from "state/UserAnnotations"; +import { initTooltips } from "util.js"; + +/** + * This component represents a button to create a new annotation. + * It is displayed in the gutter of the code editor. + * It will only display on the last row that is currently selected if any code is selected. + * + * @attr {number} row - The row number. + * + * @element d-create-annotation-button + */ +@customElement("d-create-annotation-button") +export class CreateAnnotationButton extends ShadowlessLitElement { + @property({ type: Number }) + row: number; + + get addAnnotationTitle(): string { + const key = annotationState.isQuestionMode ? "question" : "annotation"; + + + return I18n.t(`js.annotations.options.add_${key}_for_selection`); + + // return I18n.t(`js.annotations.options.add_${key}`); + } + + openForm(): void { + userAnnotationState.showForm = true; + if (!this.rangeExists) { + userAnnotationState.selectedRange = { + row: this.row, + rows: 1, + }; + } else { + window.getSelection()?.removeAllRanges(); + } + } + + get rangeExists(): boolean { + return userAnnotationState.selectedRange !== undefined && userAnnotationState.selectedRange !== null; + } + + get isRangeEnd(): boolean { + return this.rangeExists && !userAnnotationState.showForm && + userAnnotationState.selectedRange.row + (userAnnotationState.selectedRange.rows ?? 1) - 1 == this.row; + } + + protected updated(_changedProperties: PropertyValues): void { + super.updated(_changedProperties); + initTooltips(this); + } + + get rowCharLength(): number { + return this.row.toString().length; + } + + protected render(): TemplateResult { + return html` +
+
+ +
+
`; + } +} diff --git a/app/assets/javascripts/components/annotations/hidden_annotations_dot.ts b/app/assets/javascripts/components/annotations/hidden_annotations_dot.ts new file mode 100644 index 0000000000..ff656cb30c --- /dev/null +++ b/app/assets/javascripts/components/annotations/hidden_annotations_dot.ts @@ -0,0 +1,67 @@ +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { customElement, property } from "lit/decorators.js"; +import { html, TemplateResult } from "lit"; +import { MachineAnnotationData, machineAnnotationState } from "state/MachineAnnotations"; +import { UserAnnotationData, userAnnotationState } from "state/UserAnnotations"; +import { i18nMixin } from "components/meta/i18n_mixin"; +import { PropertyValues } from "@lit/reactive-element/development/reactive-element"; +import { initTooltips } from "util.js"; +import { annotationState, compareAnnotationOrders } from "state/Annotations"; + +/** + * This component represents a dot that shows the number of hidden annotations for a line. + * + * @element d-hidden-annotations-dot + * + * @prop {number} row - The row number. + */ +@customElement("d-hidden-annotations-dot") +export class HiddenAnnotationsDot extends i18nMixin(ShadowlessLitElement) { + @property({ type: Number }) + row: number; + + get machineAnnotations(): MachineAnnotationData[] { + return machineAnnotationState.byLine.get(this.row) || []; + } + + get userAnnotations(): UserAnnotationData[] { + return userAnnotationState.rootIdsByLine.get(this.row)?.map(id => userAnnotationState.byId.get(id)) || []; + } + + get hiddenAnnotations(): (MachineAnnotationData | UserAnnotationData)[] { + return [...this.machineAnnotations, ...this.userAnnotations].filter(a => !annotationState.isVisible(a)); + } + + get infoDotClasses(): string { + const hiddenType = this.hiddenAnnotations.sort(compareAnnotationOrders)[0]?.type; + return `dot-${hiddenType}`; + } + + get infoDotTitle(): string { + const count = this.hiddenAnnotations.length; + if (count === 1) { + return I18n.t("js.annotation.hidden.single"); + } else { + return I18n.t("js.annotation.hidden.plural", { count: count }); + } + } + + updated(_changedProperties: PropertyValues): void { + super.updated(_changedProperties); + initTooltips(this); + } + + render(): TemplateResult { + if (this.hiddenAnnotations.length > 0) { + return html` + + `; + } + + return html``; + } +} diff --git a/app/assets/javascripts/components/annotations/machine_annotation.ts b/app/assets/javascripts/components/annotations/machine_annotation.ts new file mode 100644 index 0000000000..93e744e0b3 --- /dev/null +++ b/app/assets/javascripts/components/annotations/machine_annotation.ts @@ -0,0 +1,57 @@ +import { html, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { MachineAnnotationData } from "state/MachineAnnotations"; +import { annotationState } from "state/Annotations"; + + +/** + * This component represents a machine annotation. + * + * @element d-machine-annotation + * + * @prop {MachineAnnotationData} data - The machine annotation data. + */ +@customElement("d-machine-annotation") +export class MachineAnnotation extends ShadowlessLitElement { + @property({ type: Object }) + data: MachineAnnotationData; + + protected get hasNotice(): boolean { + return this.data.externalUrl !== null && this.data.externalUrl !== undefined; + } + + protected get text(): string { + // Filter out lines only containing dashes. + return this.data.text.split("\n") + .filter(s => !s.match("^--*$")) + .join("\n"); + } + + render(): TemplateResult { + return html` +
+
+ + ${I18n.t(`js.annotation.type.${this.data.type}`)} + ${this.hasNotice ? html` + + ยท + + + + + ` : ""} + +
+
${this.text}
+
+ `; + } +} diff --git a/app/assets/javascripts/components/annotations/select.ts b/app/assets/javascripts/components/annotations/select.ts new file mode 100644 index 0000000000..598e786473 --- /dev/null +++ b/app/assets/javascripts/components/annotations/select.ts @@ -0,0 +1,213 @@ +import { SelectedRange, userAnnotationState } from "state/UserAnnotations"; +import { CodeListingRow } from "components/annotations/code_listing_row"; +import { annotationState } from "state/Annotations"; +import { submissionState } from "state/Submissions"; + +/** + * @param node The node to get the offset for + * @param offset The offset within the current node + * + * @returns The offset in number of characters from the start of the `closest` PRE element + * If the element is not inside a PRE element, returns undefined + */ +export function getOffset(node: Node, offset: number): number | undefined { + if (node.nodeName === "PRE") { + return offset; + } + + const parent = node.parentNode; + if (!parent) { + return undefined; + } + + let precedingText = ""; + for (const child of parent.childNodes) { + if (child === node) { + break; + } + if (child.nodeType !== Node.COMMENT_NODE) { + precedingText += child.textContent; + } + } + return getOffset(parent, offset + precedingText.length); +} + +/** + * This function translates a selection into a range within the code listing. + * If the selection is not inside a code listing row, returns undefined. + * + * Multiline selections will always return the whole line for each line in the selection. + * In this case the selection param might be modified to match the returned range. + * + * @param selection The selection to get the range for + * @returns The range of the selection in the code listing + * Unless both the start and end of the selection are inside a code listing row, returns undefined + */ +export function selectedRangeFromSelection(selection: Selection): SelectedRange | undefined { + // Selection.anchorNode does not behave as expected in firefox, see https://bugzilla.mozilla.org/show_bug.cgi?id=1420854 + // So we use the startContainer of the range instead + const anchorNode = selection.getRangeAt(0).startContainer; + const focusNode = selection.getRangeAt(selection.rangeCount - 1).endContainer; + const anchorOffset = selection.getRangeAt(0).startOffset; + const focusOffset = selection.getRangeAt(selection.rangeCount - 1).endOffset; + + const anchorRow = anchorNode?.parentElement.closest("d-code-listing-row") as CodeListingRow; + const focusRow = focusNode?.parentElement.closest("d-code-listing-row") as CodeListingRow; + + // Both the start and end of the selection must be inside a code listing row to get a valid code selection + if (!anchorRow || !focusRow) { + return undefined; + } + + // Find the exact position of the selection in the code `pre` element + // If the selection is not inside a `pre` element, we assume the offset is zero + const anchorColumn = getOffset(anchorNode, anchorOffset) || 0; + const focusColumn = getOffset(focusNode, focusOffset) || 0; + + let range: SelectedRange; + if (anchorRow.row < focusRow.row) { + range = { + row: anchorRow.row, + rows: focusRow.row - anchorRow.row + 1, + column: anchorColumn, + columns: focusColumn, + }; + } else if (anchorRow.row > focusRow.row) { + range = { + row: focusRow.row, + rows: anchorRow.row - focusRow.row + 1, + column: focusColumn, + columns: anchorColumn, + }; + } else if (anchorColumn < focusColumn) { + range = { + row: anchorRow.row, + rows: 1, + column: anchorColumn, + columns: focusColumn - anchorColumn, + }; + } else { + range = { + row: anchorRow.row, + rows: 1, + column: focusColumn, + columns: anchorColumn - focusColumn, + }; + } + const codeLines = submissionState.code.split("\n"); + + // If we have selected nothing on the last row, we don't want to include that row + // Instead end the selection on the last char of the previous row + if (range.columns === 0 && range.rows > 1) { + range.columns = undefined; + range.rows -= 1; + } + + // If we selected multiple rows, we want to select the entire rows + if (range.rows > 1) { + // If we have selected nothing on the first row, we don't want to include that row + while (codeLines[range.row - 1].length <= range.column && range.rows > 1) { + range.column = 0; + range.rows -= 1; + range.row += 1; + } + + // Ignore the columns if we have selected multiple rows + range.column = 0; + range.columns = undefined; + + // If we have selected nothing on the last row, we don't want to include that row + while (codeLines[range.row + range.rows - 2] === "" && range.rows > 1) { + range.rows -= 1; + } + + // Update the selection to match the newly calculated Selected Range + const numberOfRanges = selection.rangeCount; + selection.removeAllRanges(); + + // The number of ranges used is browser dependent + // Chrome uses one range for the entire selection and filters out non selectable elements based on css + // Firefox uses one range per continuous selection, but allows manual selection of non selectable elements + // This code should be browser agnostic as it only returns multiple ranges if the selection is not continuous + if (numberOfRanges == 1) { + const newRange = new Range(); + const startLine = document.querySelector(`#line-${range.row}`); + const endLine = document.querySelector(`#line-${range.row + range.rows - 1}`); + newRange.setStart(startLine.querySelector(".code-line"), 0); + newRange.setEnd(endLine.querySelector(".code-line"), endLine.querySelector(".code-line").childNodes.length); + selection.addRange(newRange); + } else { + for (let i = range.row; i < range.row + range.rows; i++) { + const newRange = new Range(); + const line = document.querySelector(`#line-${i}`); + newRange.setStart(line.querySelector(".code-line"), 0); + newRange.setEnd(line.querySelector(".code-line"), line.querySelector(".code-line").childNodes.length); + selection.addRange(newRange); + } + } + } + + return range; +} + +function rangeInAnnotation(range: Range): boolean { + const annotation = (range.startContainer.parentElement as Element)?.closest(".annotation") || + (range.endContainer.parentElement as Element)?.closest(".annotation"); + return annotation !== null; +} + +function anyRangeInAnnotation(selection: Selection): boolean { + for (let i = 0; i < selection.rangeCount; i++) { + if (rangeInAnnotation(selection.getRangeAt(i))) { + return true; + } + } + return false; +} + +function addSelectionClasses(): void { + const selectionType = annotationState.isQuestionMode ? "question" : "annotation"; + document.querySelector(".code-table")?.classList.add(`selection-color-${selectionType}`); + document.body.classList.add("no-selection-outside-code"); +} + +function removeSelectionClasses(): void { + document.querySelector(".code-table")?.classList.remove("selection-color-annotation", "selection-color-question"); + document.body.classList.remove("no-selection-outside-code"); +} + +export async function triggerSelectionEnd(): Promise { + if (userAnnotationState.showForm) { + removeSelectionClasses(); + return; + } + + // Wait for the selection to be updated + await new Promise(resolve => setTimeout(resolve, 100)); + const selection = window.getSelection(); + if (!selection.isCollapsed && !anyRangeInAnnotation(selection)) { + userAnnotationState.selectedRange = selectedRangeFromSelection(selection); + if (userAnnotationState.selectedRange) { + addSelectionClasses(); + } else { + removeSelectionClasses(); + } + } else { + removeSelectionClasses(); + userAnnotationState.selectedRange = undefined; + } +} + +export function triggerSelectionStart(e: PointerEvent): void { + if (e.pointerType === "mouse" && e.button !== 0) { + // ignore all mouse events except left click + return; + } + + if (!(e.target as Element).closest(".annotation") && !userAnnotationState.showForm) { + addSelectionClasses(); + if (!(e.target as Element).closest("button")) { + userAnnotationState.selectedRange = undefined; + } + } +} diff --git a/app/assets/javascripts/components/annotations/selection_marker.ts b/app/assets/javascripts/components/annotations/selection_marker.ts new file mode 100644 index 0000000000..71d1e24072 --- /dev/null +++ b/app/assets/javascripts/components/annotations/selection_marker.ts @@ -0,0 +1,25 @@ +import { LitElement, html, TemplateResult } from "lit"; +import { customElement } from "lit/decorators.js"; +import { AnnotationMarker } from "components/annotations/annotation_marker"; +import { annotationState } from "state/Annotations"; + +/** + * A marker that replaces the selection. + * The slotted content is styled to look like a user annotation. + * It is used to mark the selected code while editing an annotation + * @element d-selection-marker + */ +@customElement("d-selection-marker") +class SelectionMarker extends LitElement { + render(): TemplateResult { + return html``; + } +} diff --git a/app/assets/javascripts/components/annotations/thread.ts b/app/assets/javascripts/components/annotations/thread.ts new file mode 100644 index 0000000000..910296a278 --- /dev/null +++ b/app/assets/javascripts/components/annotations/thread.ts @@ -0,0 +1,122 @@ +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { customElement, property } from "lit/decorators.js"; +import { + + UserAnnotationData, + UserAnnotationFormData, userAnnotationState +} from "state/UserAnnotations"; +import { html, TemplateResult } from "lit"; +import { submissionState } from "state/Submissions"; +import { AnnotationForm } from "components/annotations/annotation_form"; +import { createRef, Ref, ref } from "lit/directives/ref.js"; +import { i18nMixin } from "components/meta/i18n_mixin"; +import { annotationState } from "state/Annotations"; +import { evaluationState } from "state/Evaluations"; + +/** + * This component represents a thread of annotations. + * It also contains the form for creating new annotations. + * + * @element d-thread + * + * @prop {number} rootId - the id of the root annotation for this thread + */ +@customElement("d-thread") +export class Thread extends i18nMixin(ShadowlessLitElement) { + @property({ type: Number, attribute: "root-id" }) + rootId: number; + + @property({ state: true }) + showForm = false; + + annotationFormRef: Ref = createRef(); + + get data(): UserAnnotationData { + return userAnnotationState.byId.get(this.rootId); + } + + get openQuestions(): UserAnnotationData[] | undefined { + return [this.data, ...this.data.responses] + .filter(response => response.question_state !== undefined && response.question_state !== "answered"); + } + + get isUnanswered(): boolean { + return this.openQuestions.length > 0; + } + + async createAnnotation(e: CustomEvent): Promise { + const annotationData: UserAnnotationFormData = { + "annotation_text": e.detail.text, + "line_nr": this.data.line_nr, + "evaluation_id": evaluationState.id, + "saved_annotation_id": e.detail.savedAnnotationId || undefined, + "thread_root_id": this.data.id, + }; + + try { + const mode = annotationState.isQuestionMode ? "question" : "annotation"; + await userAnnotationState.create(annotationData, submissionState.id, mode, e.detail.saveAnnotation, e.detail.savedAnnotationTitle); + this.showForm = false; + } catch (err) { + this.annotationFormRef.value.hasErrors = true; + this.annotationFormRef.value.disabled = false; + } + } + + markAsResolved(): void { + userAnnotationState.transitionAll(this.openQuestions, "answered"); + } + + markAsInProgress(): void { + userAnnotationState.transitionAll(this.openQuestions.filter(question => question.question_state !== "in_progress"), "in_progress"); + } + + markAsUnanswered(): void { + userAnnotationState.transitionAll(this.openQuestions.filter(question => question.question_state !== "unanswered"), "unanswered"); + } + + addReply(): void { + this.showForm = true; + this.markAsInProgress(); + } + + cancelReply(): void { + this.showForm = false; + this.markAsUnanswered(); + } + + render(): TemplateResult { + return this.data ? html` +
+ + ${this.data.responses.map(response => html` + + `)} + ${this.showForm ? html` +
+ this.createAnnotation(e)} + ${ref(this.annotationFormRef)} + @cancel=${() => this.cancelReply()} + submit-button-text="reply" + > +
+ ` : html` +
+ + ${this.isUnanswered ? html` + ${I18n.t("js.user_question.or")} + + ${I18n.t("js.user_question.resolve")} + + ` : html``} +
+ `} +
+ ` : html``; + } +} diff --git a/app/assets/javascripts/components/annotations/user_annotation.ts b/app/assets/javascripts/components/annotations/user_annotation.ts new file mode 100644 index 0000000000..cc10980c85 --- /dev/null +++ b/app/assets/javascripts/components/annotations/user_annotation.ts @@ -0,0 +1,211 @@ +import { html, PropertyValues, TemplateResult } from "lit"; +import { customElement, property } from "lit/decorators.js"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { UserAnnotationData, userAnnotationState } from "state/UserAnnotations"; +import { i18nMixin } from "components/meta/i18n_mixin"; +import { AnnotationForm } from "components/annotations/annotation_form"; +import { createRef, Ref, ref } from "lit/directives/ref.js"; +import "components/saved_annotations/new_saved_annotation"; +import { initTooltips } from "util.js"; +import "components/saved_annotations/saved_annotation_icon"; +import { annotationState } from "state/Annotations"; + +/** + * This component represents a single user annotation. + * It can be either a root annotation or a response. + * It also contains the form for editing the annotation. + * + * @element d-user-annotation + * + * @prop {UserAnnotationData} data - the data of the annotation + */ +@customElement("d-user-annotation") +export class UserAnnotation extends i18nMixin(ShadowlessLitElement) { + @property({ type: Object }) + data: UserAnnotationData; + + @property({ state: true }) + editing = false; + + annotationFormRef: Ref = createRef(); + + get headerText(): string { + if (!this.data.permission.can_see_annotator) { + return I18n.t("js.user_annotation.anonymous_message"); + } + + const timestamp = I18n.formatDate(this.data.created_at, "time.formats.annotation"); + const user = this.data.user?.name; + + return I18n.t("js.user_annotation.meta", { user: user, time: timestamp }); + } + + get type(): string { + return annotationState.isQuestionMode ? "user_question" : "user_annotation"; + } + + protected get header(): TemplateResult { + return html` + ${this.headerText} + ${!this.data.released ? html` + + ` : ""} + + + ${this.data.newer_submission_url ? html` + + ยท + + + + + ` : ""} + ${ this.data.question_state == "unanswered" ? html` + + ` : ""} + ${ this.data.question_state == "in_progress" ? html` + + ` : ""} + ${ this.data.question_state == "answered" ? html` + + ` : ""} + `; + } + + deleteAnnotation(): void { + if (confirm(I18n.t(`js.${this.type}.delete_confirm`))) { + userAnnotationState.delete(this.data); + } + } + + async updateAnnotation(e: CustomEvent): Promise { + try { + await userAnnotationState.update(this.data, { + annotation_text: e.detail.text, + saved_annotation_id: e.detail.savedAnnotationId || undefined, + }); + this.editing = false; + } catch (e) { + this.annotationFormRef.value.hasErrors = true; + this.annotationFormRef.value.disabled = false; + } + } + + protected updated(_changedProperties: PropertyValues): void { + super.updated(_changedProperties); + + try { + // Ask MathJax to search for math in the annotations + window.MathJax.typeset([this]); + } catch (e) { + // MathJax is not loaded + console.warn("MathJax is not loaded"); + } + // Reinitialize tooltips + initTooltips(this); + } + + reopenQuestion(): void { + userAnnotationState.transition(this.data, "unanswered"); + } + + get dropdownOptions(): TemplateResult[] { + const options = []; + + if (this.data.permission.update) { + options.push(html` +
  • + + ${I18n.t(`js.${this.type}.edit`)} + +
  • + `); + } + if (this.data.permission.save) { + options.push(html` + + + `); + } + if (this.data.permission.destroy) { + options.push(html` +
  • + + ${I18n.t(`js.user_annotation.delete`)} + +
  • + `); + } + if (this.data.permission.transition?.unanswered) { + options.push(html` +
  • + + ${I18n.t("js.user_question.unresolve")} + +
  • + `); + } + + return options; + } + + render(): TemplateResult { + return html` +
    +
    + + ${this.header} + + ${this.dropdownOptions.length > 0 ? html` + + ` : ""} +
    + ${this.editing ? html` + + ` : html` +
    + ${unsafeHTML(this.data.rendered_markdown)} +
    + `} +
    + `; + } +} diff --git a/app/assets/javascripts/components/copy_button.ts b/app/assets/javascripts/components/copy_button.ts index 8d271909d9..55d06f87f7 100644 --- a/app/assets/javascripts/components/copy_button.ts +++ b/app/assets/javascripts/components/copy_button.ts @@ -1,4 +1,4 @@ -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import { html, PropertyValues, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; import { initTooltips, ready } from "util.js"; diff --git a/app/assets/javascripts/components/course_labels_search_bar.ts b/app/assets/javascripts/components/course_labels_search_bar.ts index 447ae05db4..610a119513 100644 --- a/app/assets/javascripts/components/course_labels_search_bar.ts +++ b/app/assets/javascripts/components/course_labels_search_bar.ts @@ -1,6 +1,6 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import { Option } from "components/datalist_input"; import { ready } from "util.js"; import "components/datalist_input"; diff --git a/app/assets/javascripts/components/datalist_input.ts b/app/assets/javascripts/components/datalist_input.ts index 198ce9c3e5..9b1edf1f0d 100644 --- a/app/assets/javascripts/components/datalist_input.ts +++ b/app/assets/javascripts/components/datalist_input.ts @@ -1,8 +1,8 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import { ref, Ref, createRef } from "lit/directives/ref.js"; -import { watchMixin } from "components/watch_mixin"; +import { watchMixin } from "components/meta/watch_mixin"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { htmlEncode } from "util.js"; diff --git a/app/assets/javascripts/components/meta/i18n_mixin.ts b/app/assets/javascripts/components/meta/i18n_mixin.ts new file mode 100644 index 0000000000..19d4342349 --- /dev/null +++ b/app/assets/javascripts/components/meta/i18n_mixin.ts @@ -0,0 +1,27 @@ +import { LitElement } from "lit"; +import { ready } from "util.js"; + +type Constructor = new (...args: any[]) => LitElement; + +export function i18nMixin(superClass: T): T { + /** + * This mixin makes a LitElement that integrates I18n + * It also makes sure that the component is rerendered when the language becomes available + */ + class I18nMixinClass extends superClass { + constructor(...args: any[]) { + super(args); + // Reload when I18n is available + this.initI18n(); + } + + async initI18n(): Promise { + // Reload when I18n is available + await ready; + this.requestUpdate(); + } + } + + // Cast return type to the superClass type passed in + return I18nMixinClass as T; +} diff --git a/app/assets/javascripts/components/shadowless_lit_element.ts b/app/assets/javascripts/components/meta/shadowless_lit_element.ts similarity index 79% rename from app/assets/javascripts/components/shadowless_lit_element.ts rename to app/assets/javascripts/components/meta/shadowless_lit_element.ts index e17efb23af..c5e18bd70c 100644 --- a/app/assets/javascripts/components/shadowless_lit_element.ts +++ b/app/assets/javascripts/components/meta/shadowless_lit_element.ts @@ -1,4 +1,5 @@ import { LitElement } from "lit"; +import { StateController } from "state/state_system/StateController"; /** * This class removes the shadow dom functionality from lit elements @@ -12,6 +13,11 @@ import { LitElement } from "lit"; * When shadow dom is required just use a normal LitElement */ export class ShadowlessLitElement extends LitElement { + constructor() { + super(); + new StateController(this); + } + // don't use shadow dom createRenderRoot(): Element { return this; diff --git a/app/assets/javascripts/components/watch_mixin.ts b/app/assets/javascripts/components/meta/watch_mixin.ts similarity index 100% rename from app/assets/javascripts/components/watch_mixin.ts rename to app/assets/javascripts/components/meta/watch_mixin.ts diff --git a/app/assets/javascripts/components/modal_mixin.ts b/app/assets/javascripts/components/modal_mixin.ts index feb0929882..91a8b9632a 100644 --- a/app/assets/javascripts/components/modal_mixin.ts +++ b/app/assets/javascripts/components/modal_mixin.ts @@ -1,7 +1,7 @@ import { html, TemplateResult, render } from "lit"; import { ref } from "lit/directives/ref.js"; import { Modal as Modal } from "bootstrap"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; /** * This mixin adds support for rendering bootstrap modals within a webcomponent. diff --git a/app/assets/javascripts/components/pagination.ts b/app/assets/javascripts/components/pagination.ts index f2e659bb16..7abd1dd578 100644 --- a/app/assets/javascripts/components/pagination.ts +++ b/app/assets/javascripts/components/pagination.ts @@ -1,7 +1,8 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; -import { searchQuery } from "search"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { search } from "search"; +import { searchQueryState } from "state/SearchQuery"; /** * This component represents a pagination component as commonly found at the bottom of a paginated list page @@ -38,7 +39,7 @@ export class Pagination extends ShadowlessLitElement { } gotToPage(page: number): void { - searchQuery.queryParams.updateParam("page", page.toString()); + searchQueryState.queryParams.set("page", page.toString()); } pageButton(page?: number, text?: string): TemplateResult { diff --git a/app/assets/javascripts/components/progress_bar.ts b/app/assets/javascripts/components/progress_bar.ts index a58d2e83c6..65e821f64e 100644 --- a/app/assets/javascripts/components/progress_bar.ts +++ b/app/assets/javascripts/components/progress_bar.ts @@ -1,7 +1,7 @@ import { html, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; -import { initTooltips, ready } from "util.js"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { initTooltips, ready } from "../util"; /** * This component displays a progress bar consisting of consecutive divs diff --git a/app/assets/javascripts/components/saved_annotations/edit_saved_annotation.ts b/app/assets/javascripts/components/saved_annotations/edit_saved_annotation.ts index f8c97c12f7..d194f28df5 100644 --- a/app/assets/javascripts/components/saved_annotations/edit_saved_annotation.ts +++ b/app/assets/javascripts/components/saved_annotations/edit_saved_annotation.ts @@ -1,7 +1,7 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; -import { SavedAnnotation, updateSavedAnnotation, deleteSavedAnnotation } from "state/SavedAnnotations"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { SavedAnnotation, savedAnnotationState } from "state/SavedAnnotations"; import "./saved_annotation_form"; import { modalMixin } from "components/modal_mixin"; @@ -23,7 +23,7 @@ export class EditSavedAnnotation extends modalMixin(ShadowlessLitElement) { async updateSavedAnnotation(): Promise { try { - await updateSavedAnnotation(this.savedAnnotation.id, { + await savedAnnotationState.update(this.savedAnnotation.id, { saved_annotation: this.savedAnnotation }); this.errors = undefined; @@ -35,7 +35,7 @@ export class EditSavedAnnotation extends modalMixin(ShadowlessLitElement) { async deleteSavedAnnotation(): Promise { if (confirm(I18n.t("js.saved_annotation.delete.confirm"))) { - await deleteSavedAnnotation(this.savedAnnotation.id); + await savedAnnotationState.delete(this.savedAnnotation.id); this.hideModal(); } } diff --git a/app/assets/javascripts/components/saved_annotations/new_saved_annotation.ts b/app/assets/javascripts/components/saved_annotations/new_saved_annotation.ts index 8c0aa66c78..b9bff542d5 100644 --- a/app/assets/javascripts/components/saved_annotations/new_saved_annotation.ts +++ b/app/assets/javascripts/components/saved_annotations/new_saved_annotation.ts @@ -1,10 +1,10 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; -import { createSavedAnnotation, getSavedAnnotation, SavedAnnotation } from "state/SavedAnnotations"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { SavedAnnotation, savedAnnotationState } from "state/SavedAnnotations"; import "./saved_annotation_form"; import { modalMixin } from "components/modal_mixin"; -import { stateMixin } from "state/StateMixin"; +import { isBetaCourse } from "saved_annotation_beta"; /** * This component represents an creation button for a saved annotation @@ -17,7 +17,7 @@ import { stateMixin } from "state/StateMixin"; * @prop {Number} savedAnnotationId - the id of the saved annotation */ @customElement("d-new-saved-annotation") -export class NewSavedAnnotation extends stateMixin(modalMixin(ShadowlessLitElement)) { +export class NewSavedAnnotation extends modalMixin(ShadowlessLitElement) { @property({ type: Number, attribute: "from-annotation-id" }) fromAnnotationId: number; @property({ type: String, attribute: "annotation-text" }) @@ -34,12 +34,8 @@ export class NewSavedAnnotation extends stateMixin(modalMixin(ShadowlessLitEleme return this.savedAnnotationId != undefined; } - get state(): string[] { - return this.isAlreadyLinked ? [`getSavedAnnotation${this.savedAnnotationId}`] : []; - } - get linkedSavedAnnotation(): SavedAnnotation { - return getSavedAnnotation(this.savedAnnotationId); + return savedAnnotationState.get(this.savedAnnotationId); } get newSavedAnnotation(): SavedAnnotation { @@ -53,18 +49,12 @@ export class NewSavedAnnotation extends stateMixin(modalMixin(ShadowlessLitEleme async createSavedAnnotation(): Promise { try { - this.savedAnnotationId = await createSavedAnnotation({ + await savedAnnotationState.create({ from: this.fromAnnotationId, saved_annotation: this.savedAnnotation || this.newSavedAnnotation }); this.errors = undefined; this.hideModal(); - const event = new CustomEvent("created", { - detail: { id: this.savedAnnotationId }, - bubbles: true, - composed: true } - ); - this.dispatchEvent(event); } catch (errors) { this.errors = errors; } @@ -95,12 +85,13 @@ export class NewSavedAnnotation extends stateMixin(modalMixin(ShadowlessLitEleme } render(): TemplateResult { - return this.isAlreadyLinked && this.linkedSavedAnnotation!= undefined ? html`` : html` - this.showModal()} - > - - `; + return isBetaCourse() && !(this.isAlreadyLinked && this.linkedSavedAnnotation) ? html` +
  • + + + ${I18n.t("js.saved_annotation.new.button_title")} + +
  • + ` : html``; } } diff --git a/app/assets/javascripts/components/saved_annotations/saved_annotation_form.ts b/app/assets/javascripts/components/saved_annotations/saved_annotation_form.ts index 1bf476e2c9..ea65237715 100644 --- a/app/assets/javascripts/components/saved_annotations/saved_annotation_form.ts +++ b/app/assets/javascripts/components/saved_annotations/saved_annotation_form.ts @@ -1,6 +1,6 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import { SavedAnnotation } from "state/SavedAnnotations"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; diff --git a/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts b/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts index ff40168de5..17f4aebeea 100644 --- a/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts +++ b/app/assets/javascripts/components/saved_annotations/saved_annotation_icon.ts @@ -1,9 +1,8 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; -import { getSavedAnnotation, SavedAnnotation } from "state/SavedAnnotations"; -import "./saved_annotation_form"; -import { stateMixin } from "state/StateMixin"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { SavedAnnotation, savedAnnotationState } from "state/SavedAnnotations"; +import { isBetaCourse } from "saved_annotation_beta"; /** * Shows a link icon with some info on hover about the linked saved annotation @@ -13,25 +12,23 @@ import { stateMixin } from "state/StateMixin"; * @prop {Number} savedAnnotationId - the id of the saved annotation */ @customElement("d-saved-annotation-icon") -export class SavedAnnotationIcon extends stateMixin(ShadowlessLitElement) { +export class SavedAnnotationIcon extends ShadowlessLitElement { @property({ type: Number, attribute: "saved-annotation-id" }) - savedAnnotationId: number; + savedAnnotationId: number | null; get isAlreadyLinked(): boolean { return this.savedAnnotationId != undefined; } - get state(): string[] { - return this.isAlreadyLinked ? [`getSavedAnnotation${this.savedAnnotationId}`] : []; - } - get savedAnnotation(): SavedAnnotation { - return getSavedAnnotation(this.savedAnnotationId); + return savedAnnotationState.get(this.savedAnnotationId); } render(): TemplateResult { - return this.isAlreadyLinked && this.savedAnnotation!= undefined ? html` - + return isBetaCourse() && this.isAlreadyLinked && this.savedAnnotation!= undefined ? html` + ` : html``; } } diff --git a/app/assets/javascripts/components/saved_annotations/saved_annotation_input.ts b/app/assets/javascripts/components/saved_annotations/saved_annotation_input.ts index 6730a7cd69..0415909d11 100644 --- a/app/assets/javascripts/components/saved_annotations/saved_annotation_input.ts +++ b/app/assets/javascripts/components/saved_annotations/saved_annotation_input.ts @@ -1,10 +1,12 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import "components/datalist_input"; -import { getSavedAnnotation, getSavedAnnotations, SavedAnnotation } from "state/SavedAnnotations"; -import { stateMixin } from "state/StateMixin"; +import { SavedAnnotation, savedAnnotationState } from "state/SavedAnnotations"; import { unsafeHTML } from "lit/directives/unsafe-html.js"; +import { userState } from "state/Users"; +import { courseState } from "state/Courses"; +import { exerciseState } from "state/Exercises"; /** * This component represents an input for a saved annotation id. @@ -13,24 +15,15 @@ import { unsafeHTML } from "lit/directives/unsafe-html.js"; * @element d-saved-annotation-input * * @prop {String} name - name of the input field (used in form submit) - * @prop {Number} courseId - used to fetch saved annotations by course - * @prop {Number} exerciseId - used to fetch saved annotations by exercise - * @prop {Number} userId - used to fetch saved annotations by user * @prop {String} value - the initial saved annotation id * @prop {String} annotationText - the current text of the real annotation, used to detect if there are manual changes from the selected saved annotation * * @fires input - on value change, event details contain {title: string, id: string, annotation_text: string} */ @customElement("d-saved-annotation-input") -export class SavedAnnotationInput extends stateMixin(ShadowlessLitElement) { +export class SavedAnnotationInput extends ShadowlessLitElement { @property({ type: String }) name = ""; - @property({ type: Number, attribute: "course-id" }) - courseId: number; - @property({ type: Number, attribute: "exercise-id" }) - exerciseId: number; - @property({ type: Number, attribute: "user-id" }) - userId: number; @property({ type: String }) value: string; @property( { type: String, attribute: "annotation-text" }) @@ -39,27 +32,27 @@ export class SavedAnnotationInput extends stateMixin(ShadowlessLitElement) { @property({ state: true }) __label: string; - get state(): string[] { - return this.value ? [`getSavedAnnotation${this.value}`, "getSavedAnnotations"] : ["getSavedAnnotations"]; + get userId(): number { + return userState.id; } get label(): string { - return this.value ? getSavedAnnotation(parseInt(this.value))?.title : this.__label; + return this.value ? savedAnnotationState.get(parseInt(this.value))?.title : this.__label; } get savedAnnotations(): SavedAnnotation[] { - return getSavedAnnotations(new Map([ - ["course_id", this.courseId.toString()], - ["exercise_id", this.exerciseId.toString()], + return savedAnnotationState.getList(new Map([ + ["course_id", courseState.id.toString()], + ["exercise_id", exerciseState.id.toString()], ["user_id", this.userId.toString()], ["filter", this.__label] ])); } get potentialSavedAnnotationsExist(): boolean { - return getSavedAnnotations(new Map([ - ["course_id", this.courseId.toString()], - ["exercise_id", this.exerciseId.toString()], + return savedAnnotationState.getList(new Map([ + ["course_id", courseState.id.toString()], + ["exercise_id", exerciseState.id.toString()], ["user_id", this.userId.toString()] ])).length > 0; } diff --git a/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts b/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts index e5189021f7..0961550601 100644 --- a/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts +++ b/app/assets/javascripts/components/saved_annotations/saved_annotation_list.ts @@ -1,11 +1,10 @@ import { customElement, property } from "lit/decorators.js"; import { html, TemplateResult } from "lit"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; -import { getSavedAnnotations, getSavedAnnotationsPagination, Pagination, SavedAnnotation } from "state/SavedAnnotations"; -import { stateMixin } from "state/StateMixin"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { Pagination, SavedAnnotation, savedAnnotationState } from "state/SavedAnnotations"; import "./edit_saved_annotation"; import "components/pagination"; -import { getArrayQueryParams, getQueryParams } from "state/SearchQuery"; +import { searchQueryState } from "state/SearchQuery"; /** * This component represents a list of saved annotations @@ -18,7 +17,7 @@ import { getArrayQueryParams, getQueryParams } from "state/SearchQuery"; * @prop {Boolean} small - When present, less columns and rows will be displayed in the table */ @customElement("d-saved-annotation-list") -export class SavedAnnotationList extends stateMixin(ShadowlessLitElement) { +export class SavedAnnotationList extends ShadowlessLitElement { @property({ type: Number, attribute: "course-id" }) courseId: number; @property({ type: Number, attribute: "exercise-id" }) @@ -28,10 +27,8 @@ export class SavedAnnotationList extends stateMixin(ShadowlessLitElement) { @property({ type: Boolean }) small = false; - state = ["getSavedAnnotations", "getQueryParams", "getArrayQueryParams", "getSavedAnnotationsPagination"]; - get queryParams(): Map { - const params: Map = getQueryParams(); + const params: Map = searchQueryState.queryParams; if (this.courseId) { params.set("course_id", this.courseId.toString()); } @@ -48,15 +45,15 @@ export class SavedAnnotationList extends stateMixin(ShadowlessLitElement) { } get arrayQueryParams(): Map { - return getArrayQueryParams(); + return searchQueryState.arrayQueryParams; } get savedAnnotations(): SavedAnnotation[] { - return getSavedAnnotations(this.queryParams, this.arrayQueryParams); + return savedAnnotationState.getList(this.queryParams, this.arrayQueryParams); } get pagination(): Pagination { - return getSavedAnnotationsPagination(this.queryParams, this.arrayQueryParams); + return savedAnnotationState.getPagination(this.queryParams, this.arrayQueryParams); } render(): TemplateResult { diff --git a/app/assets/javascripts/components/saved_annotations/saved_annotations_sidecard.ts b/app/assets/javascripts/components/saved_annotations/saved_annotations_sidecard.ts index c4196c56af..d5fe7fc662 100644 --- a/app/assets/javascripts/components/saved_annotations/saved_annotations_sidecard.ts +++ b/app/assets/javascripts/components/saved_annotations/saved_annotations_sidecard.ts @@ -1,9 +1,8 @@ import { customElement, property } from "lit/decorators.js"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; import { html, TemplateResult } from "lit"; import "./saved_annotation_list"; -import { getSavedAnnotations } from "state/SavedAnnotations"; -import { stateMixin } from "state/StateMixin"; +import { savedAnnotationState } from "state/SavedAnnotations"; /** * This component represents a list of saved annotations @@ -15,7 +14,7 @@ import { stateMixin } from "state/StateMixin"; * @prop {Number} userId - used to fetch saved annotations by user */ @customElement("d-saved-annotations-sidecard") -export class SavedAnnotationList extends stateMixin(ShadowlessLitElement) { +export class SavedAnnotationList extends ShadowlessLitElement { @property({ type: Number, attribute: "course-id" }) courseId: number; @property({ type: Number, attribute: "exercise-id" }) @@ -23,10 +22,8 @@ export class SavedAnnotationList extends stateMixin(ShadowlessLitElement) { @property({ type: Number, attribute: "user-id" }) userId: number; - state = ["getSavedAnnotations"]; - get potentialSavedAnnotationsExist(): boolean { - return getSavedAnnotations(new Map([ + return savedAnnotationState.getList(new Map([ ["course_id", this.courseId.toString()], ["exercise_id", this.exerciseId.toString()], ["user_id", this.userId.toString()] diff --git a/app/assets/javascripts/components/dropdown_filter.ts b/app/assets/javascripts/components/search/dropdown_filter.ts similarity index 97% rename from app/assets/javascripts/components/dropdown_filter.ts rename to app/assets/javascripts/components/search/dropdown_filter.ts index f04c611529..31644ad410 100644 --- a/app/assets/javascripts/components/dropdown_filter.ts +++ b/app/assets/javascripts/components/search/dropdown_filter.ts @@ -1,7 +1,7 @@ import { html, TemplateResult } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { FilterCollection, Label, FilterCollectionElement } from "components/filter_collection_element"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { FilterCollection, Label, FilterCollectionElement } from "components/search/filter_collection_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; /** * This component inherits from FilterCollectionElement. diff --git a/app/assets/javascripts/components/filter_button.ts b/app/assets/javascripts/components/search/filter_button.ts similarity index 59% rename from app/assets/javascripts/components/filter_button.ts rename to app/assets/javascripts/components/search/filter_button.ts index 4c77caad4d..d1043c698a 100644 --- a/app/assets/javascripts/components/filter_button.ts +++ b/app/assets/javascripts/components/search/filter_button.ts @@ -1,8 +1,9 @@ import { customElement, property } from "lit/decorators.js"; -import { css, html, LitElement, TemplateResult } from "lit"; -import { ref } from "lit/directives/ref.js"; -import { searchQuery } from "search"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { initTooltips } from "util.js"; +import { searchQueryState } from "state/SearchQuery"; +import { StateController } from "state/state_system/StateController"; /** * This is a very simple clickable component @@ -24,8 +25,8 @@ export class FilterButton extends LitElement { value: string; @property({ type: Boolean }) multi = false; - @property({ type: Object }) - searchQuery = searchQuery; + + state = new StateController(this); static styles = css` :host { @@ -33,18 +34,20 @@ export class FilterButton extends LitElement { } `; - addFilter(): void { + addFilter(e: Event): void { + e.preventDefault(); + e.stopPropagation(); if (this.multi) { - const selected = new Set(this.searchQuery.arrayQueryParams.params.get(this.param)); + const selected = new Set(searchQueryState.arrayQueryParams.get(this.param)); selected.add(this.value); - this.searchQuery.arrayQueryParams.updateParam(this.param, Array.from(selected)); + searchQueryState.arrayQueryParams.set(this.param, Array.from(selected)); } else { - this.searchQuery.queryParams.updateParam(this.param, this.value); + searchQueryState.queryParams.set(this.param, this.value); } } render(): TemplateResult { - return html` this.addFilter()}>`; + return html` this.addFilter(e)}>`; } } @@ -61,35 +64,25 @@ export class FilterButton extends LitElement { export class FilterIcon extends ShadowlessLitElement { @property({ type: String }) value: string; - @property({ type: String }) - title: string; + @property({ type: String, attribute: "icon-title" }) + iconTitle: string; - element: Element; - - initialiseTooltip(e: Element): void { - if (e) { - this.element = e; - const tooltip = window.bootstrap.Tooltip.getInstance(this.element); - if (!tooltip) { - new window.bootstrap.Tooltip(this.element); - } - } + protected update(changedProperties: PropertyValues): void { + super.update(changedProperties); + initTooltips(this); } disconnectedCallback(): void { - const tooltip = window.bootstrap.Tooltip.getInstance(this.element); - tooltip.hide(); - super.disconnectedCallback(); + initTooltips(); } render(): TemplateResult { return html` this.initialiseTooltip(r))} > diff --git a/app/assets/javascripts/components/filter_collection_element.ts b/app/assets/javascripts/components/search/filter_collection_element.ts similarity index 64% rename from app/assets/javascripts/components/filter_collection_element.ts rename to app/assets/javascripts/components/search/filter_collection_element.ts index 8a7fd03598..967d869437 100644 --- a/app/assets/javascripts/components/filter_collection_element.ts +++ b/app/assets/javascripts/components/search/filter_collection_element.ts @@ -1,6 +1,6 @@ import { property } from "lit/decorators.js"; -import { SearchQuery, searchQuery } from "search"; -import { ShadowlessLitElement } from "components/shadowless_lit_element"; +import { ShadowlessLitElement } from "components/meta/shadowless_lit_element"; +import { searchQueryState } from "state/SearchQuery"; export type Label = {id: string, name: string}; export type FilterCollection = { @@ -30,23 +30,15 @@ export class FilterCollectionElement extends ShadowlessLitElement { paramVal: (l: Label) => string; @property({ type: Array }) labels: Array
    <% end %> <% if !@edit_submission && !@solution && @last_submission %> -
    + diff --git a/app/views/activity_read_states/_read_states_table.html.erb b/app/views/activity_read_states/_read_states_table.html.erb index 84c7534d40..c39303c40b 100644 --- a/app/views/activity_read_states/_read_states_table.html.erb +++ b/app/views/activity_read_states/_read_states_table.html.erb @@ -18,7 +18,7 @@ <% if @user.nil? && (current_user.admin? || current_user.administrating_courses.any?) %> <% unless @activity.present? %> - + <% end %> <% if read_state.course.nil? %> <%= read_state.user.full_name %> @@ -29,7 +29,7 @@ <% end %> <% unless @activity.present? %> - + <% if read_state.course.nil? %> <%= link_to read_state.activity.name, activity_path(read_state.activity) %> <% else %> diff --git a/app/views/annotations/_annotation.json.jbuilder b/app/views/annotations/_annotation.json.jbuilder index 37a9224983..9534645e44 100644 --- a/app/views/annotations/_annotation.json.jbuilder +++ b/app/views/annotations/_annotation.json.jbuilder @@ -1,4 +1,5 @@ -json.extract! annotation, :id, :line_nr, :annotation_text, :user_id, :submission_id, :saved_annotation_id, :created_at, :updated_at, :course_id +json.extract! annotation, :id, :line_nr, :annotation_text, :user_id, :submission_id, :saved_annotation_id, :created_at, :updated_at, :course_id, :column, :rows, :columns +json.row annotation.line_nr || 0 if annotation.is_a?(Question) json.extract! annotation, :question_state json.newer_submission_url(annotation.newer_submission&.then { |s| submission_url(s) }) @@ -25,6 +26,7 @@ end json.permission do json.update policy(annotation).update? json.destroy policy(annotation).destroy? + json.save SavedAnnotationPolicy.new(current_user, annotation).create? json.transition do Question.question_states.each_key do |state| json.set! state, policy(annotation).transition?(state) @@ -34,3 +36,8 @@ json.permission do end json.released AnnotationPolicy.new(annotation.submission.user, annotation).show? json.type annotation.type&.downcase + +json.responses annotation.responses do |response| + json.partial! response, as: :annotation +end +json.thread_root_id annotation.thread_root_id diff --git a/app/views/annotations/_questions_table.html.erb b/app/views/annotations/_questions_table.html.erb index ac3c1105af..45bfeeeb51 100644 --- a/app/views/annotations/_questions_table.html.erb +++ b/app/views/annotations/_questions_table.html.erb @@ -1,81 +1,74 @@ -<% content_for :javascripts do %> - <%= javascript_include_tag 'questions' %> -<% end %> - -
    - - - - - - - - - - - <% questions.each do |question| %> - - + + <% end %> + +
    - - <%= link_to question.user.full_name, course_member_path(question.submission.course, question.user), title: question.user.full_name, class: "ellipsis-overflow" %> + + + + + + + + + <% questions.each do |question| %> + + - - - - + + + - - - <% end %> - -
    +
    <% unless @course.present? %> -
    - - <%= question.submission.course.name %> - -
    + <% end %> -
    - - <%= link_to question.submission.exercise.name, course_exercise_path(question.submission.course, question.submission.exercise) %> -
    - <%= question.question_text %> + <%= question.user.full_name %> +
    + <% unless @course.present? %> +
    + <%= question.submission.course.name %>
    -
    - <% status = Question.human_enum_name(:question_state, question.question_state) %> - - <%= t "courses.questions.last_edited_by.#{question.question_state}", who: question.last_updated_by.full_name %> - - - - <%= t "courses.questions.ago", when: time_ago_in_words(question.created_at) %> - - + <% end %> + +
    + <% unless @course.present? %> + + <% end %> + <%= question.submission.exercise.name %> +
    +
    "> + <%= question.question_text %> +
    +
    + + <%= t "courses.questions.ago", when: time_ago_in_words(question.created_at) %> + +
    + <%= t "courses.questions.last_edited_by.#{question.question_state}", who: question.last_updated_by.full_name %> +
    +
    + + + + - <%= link_to submission_path(question.submission, anchor: 'code'), title: t("questions.question.view"), 'data-submission_id': question.submission.id do %> - +
  • + <%= link_to annotation_path(question), data: { from: question.question_state, to: :answered }, class: 'dropdown-item state-changer' do %> + <%= t('courses.questions.buttons.to_resolved') %> + <% end %> +
  • <% end %> -
    - -<% if questions.try(:total_pages) %> -
    <%= page_navigation_links questions, true, "annotations", {}, 'question_index' %>
    -<% end %> - + +
    diff --git a/app/views/annotations/_questions_table_with_pagination.html.erb b/app/views/annotations/_questions_table_with_pagination.html.erb new file mode 100644 index 0000000000..8d568e6ce2 --- /dev/null +++ b/app/views/annotations/_questions_table_with_pagination.html.erb @@ -0,0 +1,4 @@ +<%= render partial: 'questions_table', locals: { questions: questions } %> +<% if questions.try(:total_pages) %> +
    <%= page_navigation_links questions, true, "annotations", {}, 'question_index' %>
    +<% end %> diff --git a/app/views/annotations/question_index.html.erb b/app/views/annotations/question_index.html.erb index 95a47f63cf..cd2b08c4aa 100644 --- a/app/views/annotations/question_index.html.erb +++ b/app/views/annotations/question_index.html.erb @@ -1,3 +1,7 @@ +<% content_for :javascripts do %> + <%= javascript_include_tag 'questions' %> +<% end %> +
    @@ -16,17 +20,22 @@
    -
    +
    <% actions = [] %> <% if current_user&.a_course_admin? %> <% actions << { text: t('questions.index.watch'), search: { refresh: true }, click: 'window.dodona.toggleIndexReload()' } %> <% actions << { text: t('questions.index.everything'), search: { everything: true } } if @unfiltered %> <% end %> - <%= render partial: 'layouts/searchbar', locals: { actions: actions, refresh_element: "#refresh-element", courses: @courses, question_states: Question.question_states.keys } %> + <%= render partial: 'layouts/searchbar', locals: { actions: actions, refresh_element: "#question-container", courses: @courses, question_states: Question.question_states.keys } %>
    - <%= render partial: 'questions_table', locals: { questions: @questions } %> + <%= render partial: 'questions_table_with_pagination', locals: { questions: @questions } %>
    + diff --git a/app/views/annotations/question_index.js.erb b/app/views/annotations/question_index.js.erb index 0a1ce1e060..ea3fa12498 100644 --- a/app/views/annotations/question_index.js.erb +++ b/app/views/annotations/question_index.js.erb @@ -1 +1 @@ -$("#questions-table-wrapper").html("<%= escape_javascript(render partial: 'questions_table', locals: {questions: @questions}) %>"); +$("#questions-table-wrapper").html("<%= escape_javascript(render partial: 'questions_table_with_pagination', locals: {questions: @questions}) %>"); diff --git a/app/views/courses/_question_table.html.erb b/app/views/courses/_question_table.html.erb index 859b0227d1..fe407f23c3 100644 --- a/app/views/courses/_question_table.html.erb +++ b/app/views/courses/_question_table.html.erb @@ -1,56 +1,5 @@ <% if questions.present? %> -
    - - - - - - - - - <% questions.each do |question| %> - - - - - - - <% end %> - -
    - <%= question.user.full_name %> - - <%= question.submission.exercise.name %> -
    - <%= question.question_text %> -
    -
    - - <%= t "courses.questions.ago", when: time_ago_in_words(question.created_at) %> - - <% unless question.unanswered? %> -
    - <%= t "courses.questions.last_edited_by.#{question.question_state}", who: question.last_updated_by.full_name %> -
    - <% end %> -
    - <% if policy(question).transition?(:unanswered) %> - <%= link_to annotation_path(question), data: { from: question.question_state, to: :unanswered }, class: 'btn btn-icon state-changer' do %> - - <% end %> - <% end %> - <% if policy(question).transition?(:in_progress) && !question.answered? %> - <%= link_to annotation_path(question), data: { from: question.question_state, to: :in_progress }, class: 'btn btn-icon state-changer' do %> - - <% end %> - <% end %> - <% if policy(question).transition?(:answered) %> - <%= link_to annotation_path(question), data: { from: question.question_state, to: :answered }, class: 'btn btn-icon state-changer' do %> - - <% end %> - <% end %> -
    -
    + <%= render partial: "annotations/questions_table", locals: { questions: questions } %> <% else %> <%= t("courses.questions.no_questions.#{state}") %> <% end %> diff --git a/app/views/courses/_reload_users.js.erb b/app/views/courses/_reload_users.js.erb index a96289eb36..b4111cc079 100644 --- a/app/views/courses/_reload_users.js.erb +++ b/app/views/courses/_reload_users.js.erb @@ -4,4 +4,4 @@ <% end %> <% count = @course.pending_members.count %> $("#pending-count").text('<%= count if count.nonzero? %>'); -dodona.searchQuery.search(); +dodona.search.search(); diff --git a/app/views/evaluations/refresh_users.js.erb b/app/views/evaluations/refresh_users.js.erb index 5065ce20d0..973260cc4b 100644 --- a/app/views/evaluations/refresh_users.js.erb +++ b/app/views/evaluations/refresh_users.js.erb @@ -1,3 +1,3 @@ -window.dodona.searchQuery.search(); +window.dodona.search.search(); $('#users-count-wrapper').html("<%= t('evaluations.edit_users.users_selected_html', count: @evaluation.users.count) %>") $('#short-users-count-wrapper').text("<%= t('evaluations.edit_users.short_users_selected', count: @evaluation.users.count) %>") diff --git a/app/views/feedbacks/show.html.erb b/app/views/feedbacks/show.html.erb index 12a9aea6e7..a9381fc701 100644 --- a/app/views/feedbacks/show.html.erb +++ b/app/views/feedbacks/show.html.erb @@ -91,9 +91,8 @@ <% if @feedback.submission.present? %> diff --git a/app/views/layouts/_searchbar.html.erb b/app/views/layouts/_searchbar.html.erb index 6d4503b597..b76dc951d4 100644 --- a/app/views/layouts/_searchbar.html.erb +++ b/app/views/layouts/_searchbar.html.erb @@ -181,13 +181,13 @@ }; <% end %> - dodona.searchQuery.setRefreshElement("<%= local_assigns.fetch :refresh_element, "" %>"); + dodona.search.setRefreshElement("<%= local_assigns.fetch :refresh_element, "" %>"); // load the given localStorageKey if present. This key is used to retrieve the values stored in localStorage const localStorageKey = "<%= local_assigns.fetch :local_storage_key, "" %>" - dodona.searchQuery.setBaseUrl("<%= local_assigns.fetch :baseUrl, "" %>"); - dodona.searchQuery.autoSearch = <%= local_assigns.fetch :autoSearch, true %>; - dodona.searchQuery.setLocalStorageKey(localStorageKey); + dodona.search.setBaseUrl("<%= local_assigns.fetch :baseUrl, "" %>"); + dodona.search.autoSearch = <%= local_assigns.fetch :autoSearch, true %>; + dodona.search.setLocalStorageKey(localStorageKey); const searchFields = Array.from(document.getElementsByTagName("d-search-field")); searchFields.forEach( searchField => { diff --git a/app/views/pages/home.html.erb b/app/views/pages/home.html.erb index ef6ce1d562..6eaca505be 100644 --- a/app/views/pages/home.html.erb +++ b/app/views/pages/home.html.erb @@ -54,6 +54,6 @@ diff --git a/app/views/submissions/_submission.html.erb b/app/views/submissions/_submission.html.erb index 6d476dbd01..c9939d23b5 100644 --- a/app/views/submissions/_submission.html.erb +++ b/app/views/submissions/_submission.html.erb @@ -14,7 +14,7 @@ <% if user.nil? && (current_user.admin? || current_user.administrating_courses.any?) %> - + <% if submission.course.nil? %> <%= submission.user.full_name %> <% else %> @@ -24,7 +24,7 @@ <% end %> <% if !exercise %> - + <% if submission.course.nil? %> <%= link_to submission.exercise.name, activity_path(submission.exercise) %> <% else %> diff --git a/app/views/submissions/show.html.erb b/app/views/submissions/show.html.erb index e382d983bf..8154a01bac 100644 --- a/app/views/submissions/show.html.erb +++ b/app/views/submissions/show.html.erb @@ -16,7 +16,7 @@ <%= render partial: 'description', locals: {submission: @submission} %> `' end end diff --git a/test/system/annotations_test.rb b/test/system/annotations_test.rb index 4db547ce70..38fbe9f897 100644 --- a/test/system/annotations_test.rb +++ b/test/system/annotations_test.rb @@ -50,7 +50,7 @@ class AnnotationsTest < ApplicationSystemTestCase (1..@code_lines.length).each do |index| line = "tr#line-#{index}" find(line).hover - assert_css 'button.annotation-button' + assert_css '.annotation-button button' end end end @@ -60,7 +60,7 @@ class AnnotationsTest < ApplicationSystemTestCase click_link 'Code' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click within '.code-listing' do @code_lines.each do |code_line| assert_text code_line @@ -74,7 +74,7 @@ class AnnotationsTest < ApplicationSystemTestCase click_link 'Code' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click initial = 'This is a single line comment' within 'form.annotation-submission' do @@ -94,7 +94,7 @@ class AnnotationsTest < ApplicationSystemTestCase click_link 'Code' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click initial = 'This is a single line comment' within 'form.annotation-submission' do @@ -111,7 +111,7 @@ class AnnotationsTest < ApplicationSystemTestCase click_link 'Code' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click within 'form.annotation-submission' do click_button 'Cancel' end @@ -130,7 +130,9 @@ class AnnotationsTest < ApplicationSystemTestCase end assert_selector('.annotation', count: 1) - find('.annotation .annotation-control-button.annotation-edit i.mdi.mdi-pencil').click + dropdown = find('.annotation .dropdown') + dropdown.click + dropdown.find('i.mdi.mdi-pencil').click replacement = Faker::Lorem.paragraph(sentence_count: 3) within 'form.annotation-submission' do @@ -155,12 +157,10 @@ class AnnotationsTest < ApplicationSystemTestCase end assert_selector '.annotation', count: 1 - find('.annotation .annotation-control-button.annotation-edit i.mdi.mdi-pencil').click - - within 'form.annotation-submission' do - click_button 'Delete' - accept_confirm('Are you sure you want to delete this comment?') - end + dropdown = find('.annotation .dropdown') + dropdown.click + dropdown.find('i.mdi.mdi-delete').click + accept_confirm('Are you sure you want to delete this comment?') assert_no_css '.annotation' end @@ -199,7 +199,10 @@ class AnnotationsTest < ApplicationSystemTestCase assert_text annot.annotation_text end - find('.annotation .annotation-control-button.annotation-edit i.mdi.mdi-pencil').click + dropdown = find('.annotation .dropdown') + dropdown.click + dropdown.find('i.mdi.mdi-pencil').click + replacement = Faker::Lorem.characters number: 10_010 assert_selector 'form.annotation-submission', count: 1 # Attempt to type more than 10.000 characters. @@ -222,7 +225,9 @@ class AnnotationsTest < ApplicationSystemTestCase assert_text annot.annotation_text end - find('.annotation .annotation-control-button.annotation-edit i.mdi.mdi-pencil').click + dropdown = find('.annotation .dropdown') + dropdown.click + dropdown.find('i.mdi.mdi-pencil').click replacement = '' within 'form.annotation-submission' do find('textarea.annotation-submission-input').fill_in with: replacement @@ -256,7 +261,7 @@ class AnnotationsTest < ApplicationSystemTestCase click_link 'Code' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click initial = '' within 'form.annotation-submission' do @@ -281,7 +286,7 @@ class AnnotationsTest < ApplicationSystemTestCase click_link 'Code' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click initial = Faker::Lorem.characters(number: 10_010) within 'form.annotation-submission' do @@ -301,7 +306,7 @@ class AnnotationsTest < ApplicationSystemTestCase click_button 'Add global comment' initial = Faker::Lorem.words(number: 128).join(' ') - within '#feedback-table-global-annotations' do + within 'd-annotation-form' do find('textarea.annotation-submission-input').fill_in with: initial click_button 'Comment' end @@ -333,7 +338,9 @@ class AnnotationsTest < ApplicationSystemTestCase end old_text = annot.annotation_text - find('.annotation .annotation-control-button.annotation-edit i.mdi.mdi-pencil').click + dropdown = find('.annotation .dropdown') + dropdown.click + dropdown.find('i.mdi.mdi-pencil').click replacement = Faker::Lorem.words(number: 32).join(' ') within 'form.annotation-submission' do find('textarea.annotation-submission-input').fill_in with: replacement @@ -353,4 +360,85 @@ class AnnotationsTest < ApplicationSystemTestCase end assert_no_css 'form.annotation-submission' end + + test 'Can reply to an annotation' do + create :annotation, submission: @instance, user: @zeus + visit(submission_path(id: @instance.id)) + click_link 'Code' + + thread = find('d-thread') + within thread do + assert_selector '.annotation', count: 1 + + fake_answer_input = find('input') + fake_answer_input.click + + answer = Faker::Lorem.sentence + answer_field = find('textarea') + answer_field.fill_in with: answer + + click_button 'Reply' + + assert_selector '.annotation', count: 2 + end + end + + test 'Cannot delete an annotation with replies' do + annot = create :annotation, submission: @instance, user: @zeus + create :annotation, submission: @instance, user: @zeus, thread_root: annot + visit(submission_path(id: @instance.id)) + click_link 'Code' + + within 'd-thread' do + assert_selector '.annotation', count: 2 + + root = first('.annotation') + dropdown = root.find('.dropdown') + dropdown.click + assert_no_selector 'i.mdi.mdi-delete' + end + end + + test 'Can delete an annotation after deleting all replies' do + annot = create :annotation, submission: @instance, user: @zeus + create :annotation, submission: @instance, user: @zeus, thread_root: annot + visit(submission_path(id: @instance.id)) + click_link 'Code' + + within 'd-thread' do + assert_selector '.annotation', count: 2 + + child = all('.annotation').last + dropdown = child.find('.dropdown') + dropdown.click + dropdown.find('i.mdi.mdi-delete').click + accept_confirm('Are you sure you want to delete this comment?') + + assert_selector '.annotation', count: 1 + root = first('.annotation') + dropdown = root.find('.dropdown') + dropdown.click + assert_selector 'i.mdi.mdi-delete' + end + end + + test 'can delete the middle annotation in a thread' do + annot = create :annotation, submission: @instance, user: @zeus + create :annotation, submission: @instance, user: @zeus, thread_root: annot + create :annotation, submission: @instance, user: @zeus, thread_root: annot + visit(submission_path(id: @instance.id)) + click_link 'Code' + + within 'd-thread' do + assert_selector '.annotation', count: 3 + + child = all('.annotation')[1] + dropdown = child.find('.dropdown') + dropdown.click + dropdown.find('i.mdi.mdi-delete').click + accept_confirm('Are you sure you want to delete this comment?') + + assert_selector '.annotation', count: 2 + end + end end diff --git a/test/system/questions_test.rb b/test/system/questions_test.rb index f85685fce8..a6d037fb8f 100644 --- a/test/system/questions_test.rb +++ b/test/system/questions_test.rb @@ -16,6 +16,13 @@ class QuestionsTest < ApplicationSystemTestCase @submission.exercise.judge.save @student = @submission.user sign_in @student + + @orig = Delayed::Worker.delay_jobs + Delayed::Worker.delay_jobs = true + end + + teardown do + Delayed::Worker.delay_jobs = @orig end test 'Can ask question for each line of the available lines of code' do @@ -29,7 +36,7 @@ class QuestionsTest < ApplicationSystemTestCase line_element.hover within line_element do - button = find('button.annotation-button') + button = find('.annotation-button button') button.click assert_css 'form.annotation-submission' # cancel form to limit page space taken @@ -46,8 +53,7 @@ class QuestionsTest < ApplicationSystemTestCase click_link 'Code' within '.code-table' do - button = find('#add_global_annotation') - button.click + click_button 'Ask a question about your code' assert_css 'form.annotation-submission' end end @@ -59,8 +65,7 @@ class QuestionsTest < ApplicationSystemTestCase question = Faker::Lorem.question within '.code-table' do - button = find('#add_global_annotation') - button.click + click_button 'Ask a question about your code' form = find('form.annotation-submission') @@ -89,12 +94,11 @@ class QuestionsTest < ApplicationSystemTestCase visit(submission_path(id: @submission.id)) click_link 'Code' - question_div = find('div.annotation.question') - within question_div do - resolve_button = find('.question-control-button.question-resolve') + thread = find('d-thread') + within thread do + resolve_button = find('.btn', text: 'Mark as answered') resolve_button.click - assert_no_css '.question-control-button.question-resolve' - # Wait until ajax call is complete + assert_no_css '.mdi-comment-question-outline' end assert_equal 1, Question.count, 'There should still only be one question' @@ -102,4 +106,87 @@ class QuestionsTest < ApplicationSystemTestCase assert_not q.unanswered?, 'Question should have moved onto answered status' assert q.answered?, 'Question should have moved onto answered status' end + + test 'Responding to a question should mark the question as answered' do + q = create :question, submission: @submission, user: @student + assert_equal 1, Question.count, 'Test is invalid if magically no or more questions appear here' + assert q.unanswered?, 'Question should start as unanswered' + + visit(submission_path(id: @submission.id)) + click_link 'Code' + + thread = find('d-thread') + within thread do + assert_selector '.annotation', count: 1 + + fake_answer_input = find('input') + fake_answer_input.click + + answer = Faker::Lorem.sentence + answer_field = find('textarea') + answer_field.fill_in with: answer + + click_button 'Reply' + + assert_selector '.annotation', count: 2 + end + + assert_equal 2, Question.count, 'There should be two questions now' + assert_not q.reload.unanswered?, 'Question should have moved onto answered status' + assert q.reload.answered?, 'Question should have moved onto answered status' + end + + test 'An unanswered question should contain an icon to visualize its status' do + q = create :question, submission: @submission, user: @student + assert_equal 1, Question.count, 'Test is invalid if magically no or more questions appear here' + assert q.unanswered?, 'Question should start as unanswered' + + visit(submission_path(id: @submission.id)) + click_link 'Code' + + thread = find('d-thread') + within thread do + assert_selector '.mdi-comment-question-outline' + end + end + + test 'The status icon should change to in progress when someone clicks reply' do + q = create :question, submission: @submission, user: @student + assert_equal 1, Question.count, 'Test is invalid if magically no or more questions appear here' + assert q.unanswered?, 'Question should start as unanswered' + + visit(submission_path(id: @submission.id)) + click_link 'Code' + + thread = find('d-thread') + within thread do + assert_selector '.mdi-comment-question-outline' + fake_answer_input = find('input') + fake_answer_input.click + assert_selector '.mdi-comment-processing-outline' + assert q.reload.in_progress?, 'Question should have moved onto in progress status' + end + end + + test 'The question becomes unanswered again when a teacher cancels the reply' do + q = create :question, submission: @submission, user: @student + assert_equal 1, Question.count, 'Test is invalid if magically no or more questions appear here' + assert q.unanswered?, 'Question should start as unanswered' + + visit(submission_path(id: @submission.id)) + click_link 'Code' + + thread = find('d-thread') + within thread do + assert_selector '.mdi-comment-question-outline' + fake_answer_input = find('input') + fake_answer_input.click + assert_selector '.mdi-comment-processing-outline' + assert q.reload.in_progress?, 'Question should have moved onto in progress status' + + click_button 'Cancel' + assert_selector '.mdi-comment-question-outline' + assert q.reload.unanswered?, 'Question should have moved onto unanswered status' + end + end end diff --git a/test/system/saved_annotation_test.rb b/test/system/saved_annotation_test.rb index 4a63d4ed3c..c697a35808 100644 --- a/test/system/saved_annotation_test.rb +++ b/test/system/saved_annotation_test.rb @@ -34,7 +34,7 @@ class SavedAnnotationsTest < ApplicationSystemTestCase assert_no_css 'd-saved-annotations-sidecard' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click initial = 'The first five words of this comment will be used as the title' within 'form.annotation-submission' do @@ -70,7 +70,7 @@ class SavedAnnotationsTest < ApplicationSystemTestCase assert_no_css 'd-saved-annotations-sidecard' find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click initial = 'The first five words of this comment will be used as the title' within 'form.annotation-submission' do @@ -99,7 +99,7 @@ class SavedAnnotationsTest < ApplicationSystemTestCase # assert_css `d-saved-annotations-sidecard td[title="#{sa.title}"]` find('tr#line-1').hover - find('button.annotation-button').click + find('.annotation-button button').click within 'form.annotation-submission' do assert_css 'd-saved-annotation-input' diff --git a/test/system/search_test.rb b/test/system/search_test.rb new file mode 100644 index 0000000000..af00b0e8ed --- /dev/null +++ b/test/system/search_test.rb @@ -0,0 +1,55 @@ +require 'capybara/minitest' +require 'application_system_test_case' + +class SearchTest < ApplicationSystemTestCase + include Devise::Test::IntegrationHelpers + # Make the Capybara DSL available in all integration tests + include Capybara::DSL + # Make `assert_*` methods behave like Minitest assertions + include Capybara::Minitest::Assertions + + def assert_path_with_query(path, **query_params) + page.assert_current_path path, ignore_query: true + query_params.each do |key, value| + page.assert_current_path(/#{key}=#{value}/) + end + end + + test 'Browser history updates current entry when searching' do + 32.times do |i| + create :exercise, name_en: "test #{i}", name_nl: "test #{i}" + end + sign_in create(:zeus) + visit root_path + visit activities_path + assert_path_with_query activities_path + find('d-search-field input').send_keys 'test' + assert_path_with_query activities_path, filter: 'test' + find('.next_page').click + assert_path_with_query activities_path, filter: 'test', page: 2 + page.go_back + assert_path_with_query activities_path, filter: 'test' + page.go_back + page.assert_current_path root_path + page.go_forward + assert_path_with_query activities_path, filter: 'test' + page.go_forward + assert_path_with_query activities_path, filter: 'test', page: 2 + find('d-search-field input').send_keys 's' + assert_path_with_query activities_path, filter: 'tests' + page.go_back + assert_path_with_query activities_path, filter: 'test', page: 2 + page.go_forward + assert_path_with_query activities_path, filter: 'tests' + end + + test 'Going to a page with search does not create an extra history entry' do + sign_in create(:zeus) + visit root_path + click_on 'Toggle drawer' + click_on 'Exercises' + page.assert_current_path activities_path + page.go_back + page.assert_current_path root_path + end +end diff --git a/test/testhelpers/delayed_job_helper.rb b/test/testhelpers/delayed_job_helper.rb index 184b9fcd3d..a15b198054 100644 --- a/test/testhelpers/delayed_job_helper.rb +++ b/test/testhelpers/delayed_job_helper.rb @@ -2,4 +2,15 @@ module DelayedJobHelper def assert_jobs_enqueued(number, &) assert_difference('Delayed::Job.count', number, &) end + + def with_delayed_jobs + orig = Delayed::Worker.delay_jobs + Delayed::Worker.delay_jobs = true + yield + Delayed::Worker.delay_jobs = orig + end + + def run_delayed_jobs + Delayed::Job.find_each(batch_size: 100) { |d| Delayed::Worker.new.run(d) } + end end diff --git a/tsconfig.json b/tsconfig.json index 88655c3235..d7edac46ec 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,11 +11,11 @@ "downlevelIteration": true, "baseUrl": "app", "paths": { - "*": ["assets/javascripts/*"] + "*": ["assets/javascripts/*"], }, "pretty": true, "allowUmdGlobalAccess": true, - "resolveJsonModule": true + "resolveJsonModule": true, }, "exclude": ["**/*.spec.ts", "node_modules", "public", "data"], "compileOnSave": false diff --git a/yarn.lock b/yarn.lock index 55f33a6043..57a151bd3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,50 +10,45 @@ "@jridgewell/gen-mapping" "^0.1.0" "@jridgewell/trace-mapping" "^0.3.9" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39" + integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g== dependencies: "@babel/highlight" "^7.18.6" -"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30" - integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ== - -"@babel/compat-data@^7.20.5": - version "7.20.10" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.10.tgz#9d92fa81b87542fff50e848ed585b4212c1d34ec" - integrity sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg== +"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.21.5": + version "7.21.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.21.7.tgz#61caffb60776e49a57ba61a88f02bedd8714f6bc" + integrity sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA== -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.21.3", "@babel/core@^7.7.5": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.3.tgz#cf1c877284a469da5d1ce1d1e53665253fae712e" - integrity sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw== +"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.21.8", "@babel/core@^7.7.5": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4" + integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.3" - "@babel/helper-compilation-targets" "^7.20.7" - "@babel/helper-module-transforms" "^7.21.2" - "@babel/helpers" "^7.21.0" - "@babel/parser" "^7.21.3" + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helpers" "^7.21.5" + "@babel/parser" "^7.21.8" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.3" - "@babel/types" "^7.21.3" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.2" semver "^6.3.0" -"@babel/generator@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.3.tgz#232359d0874b392df04045d72ce2fd9bb5045fce" - integrity sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA== +"@babel/generator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.21.5.tgz#c0c0e5449504c7b7de8236d99338c3e2a340745f" + integrity sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w== dependencies: - "@babel/types" "^7.21.3" + "@babel/types" "^7.21.5" "@jridgewell/gen-mapping" "^0.3.2" "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" @@ -73,13 +68,13 @@ "@babel/helper-explode-assignable-expression" "^7.18.6" "@babel/types" "^7.18.6" -"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0", "@babel/helper-compilation-targets@^7.20.7": - version "7.20.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz#a6cd33e93629f5eb473b021aac05df62c4cd09bb" - integrity sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ== +"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz#631e6cc784c7b660417421349aac304c94115366" + integrity sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w== dependencies: - "@babel/compat-data" "^7.20.5" - "@babel/helper-validator-option" "^7.18.6" + "@babel/compat-data" "^7.21.5" + "@babel/helper-validator-option" "^7.21.0" browserslist "^4.21.3" lru-cache "^5.1.1" semver "^6.3.0" @@ -106,13 +101,13 @@ "@babel/helper-annotate-as-pure" "^7.18.6" regexpu-core "^5.1.0" -"@babel/helper-create-regexp-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b" - integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw== +"@babel/helper-create-regexp-features-plugin@^7.20.5": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.21.4.tgz#40411a8ab134258ad2cf3a3d987ec6aa0723cee5" + integrity sha512-M00OuhU+0GyZ5iBBN9czjugzWrEq2vDpf/zCYHxxf93ul/Q5rv+a5h+/+0WnI1AebHNVtl5bFV0qsJoH23DbfA== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - regexpu-core "^5.1.0" + regexpu-core "^5.3.1" "@babel/helper-define-polyfill-provider@^0.3.3": version "0.3.3" @@ -126,16 +121,16 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" - integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== - "@babel/helper-environment-visitor@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== +"@babel/helper-environment-visitor@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz#c769afefd41d171836f7cb63e295bedf689d48ba" + integrity sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ== + "@babel/helper-explode-assignable-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" @@ -143,14 +138,6 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" - integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== - dependencies: - "@babel/template" "^7.18.6" - "@babel/types" "^7.18.6" - "@babel/helper-function-name@^7.18.9": version "7.18.9" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" @@ -159,14 +146,6 @@ "@babel/template" "^7.18.6" "@babel/types" "^7.18.9" -"@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - "@babel/helper-function-name@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4" @@ -196,26 +175,26 @@ dependencies: "@babel/types" "^7.21.0" -"@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== +"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af" + integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg== dependencies: - "@babel/types" "^7.18.6" + "@babel/types" "^7.21.4" -"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.21.2": - version "7.21.2" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz#160caafa4978ac8c00ac66636cb0fa37b024e2d2" - integrity sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ== +"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz#d937c82e9af68d31ab49039136a222b17ac0b420" + integrity sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw== dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.20.2" + "@babel/helper-environment-visitor" "^7.21.5" + "@babel/helper-module-imports" "^7.21.4" + "@babel/helper-simple-access" "^7.21.5" "@babel/helper-split-export-declaration" "^7.18.6" "@babel/helper-validator-identifier" "^7.19.1" "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.2" - "@babel/types" "^7.21.2" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" @@ -224,20 +203,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629" - integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ== - -"@babel/helper-remap-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz#fa1f81acd19daee9d73de297c0308783cd3cfc23" - integrity sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.6" - "@babel/helper-wrap-function" "^7.18.6" - "@babel/types" "^7.18.6" +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" + integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== "@babel/helper-remap-async-to-generator@^7.18.9": version "7.18.9" @@ -249,7 +218,7 @@ "@babel/helper-wrap-function" "^7.18.9" "@babel/types" "^7.18.9" -"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1", "@babel/helper-replace-supers@^7.20.7": +"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7": version "7.20.7" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz#243ecd2724d2071532b2c8ad2f0f9f083bcae331" integrity sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A== @@ -261,19 +230,12 @@ "@babel/traverse" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9" - integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA== - dependencies: - "@babel/types" "^7.20.2" - -"@babel/helper-skip-transparent-expression-wrappers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818" - integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw== +"@babel/helper-simple-access@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee" + integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg== dependencies: - "@babel/types" "^7.18.9" + "@babel/types" "^7.21.5" "@babel/helper-skip-transparent-expression-wrappers@^7.20.0": version "7.20.0" @@ -289,10 +251,10 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-string-parser@^7.19.4": - version "7.19.4" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" - integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== +"@babel/helper-string-parser@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd" + integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w== "@babel/helper-validator-identifier@^7.18.6": version "7.18.6" @@ -304,21 +266,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== -"@babel/helper-validator-option@^7.18.6", "@babel/helper-validator-option@^7.21.0": +"@babel/helper-validator-option@^7.21.0": version "7.21.0" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180" integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ== -"@babel/helper-wrap-function@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz#ec44ea4ad9d8988b90c3e465ba2382f4de81a073" - integrity sha512-I5/LZfozwMNbwr/b1vhhuYD+J/mU+gfGAj5td7l5Rv9WYmH6i3Om69WGKNmlIpsVW/mF6O5bvTKbvDQZVgjqOw== - dependencies: - "@babel/helper-function-name" "^7.18.6" - "@babel/template" "^7.18.6" - "@babel/traverse" "^7.18.6" - "@babel/types" "^7.18.6" - "@babel/helper-wrap-function@^7.18.9": version "7.18.11" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.11.tgz#bff23ace436e3f6aefb61f85ffae2291c80ed1fb" @@ -329,14 +281,14 @@ "@babel/traverse" "^7.18.11" "@babel/types" "^7.18.10" -"@babel/helpers@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.0.tgz#9dd184fb5599862037917cdc9eecb84577dc4e7e" - integrity sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA== +"@babel/helpers@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.21.5.tgz#5bac66e084d7a4d2d9696bdf0175a93f7fb63c08" + integrity sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA== dependencies: "@babel/template" "^7.20.7" - "@babel/traverse" "^7.21.0" - "@babel/types" "^7.21.0" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" "@babel/highlight@^7.18.6": version "7.18.6" @@ -347,10 +299,10 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.3.tgz#1d285d67a19162ff9daa358d4cb41d50c06220b3" - integrity sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.21.5", "@babel/parser@^7.21.8": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" + integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" @@ -359,22 +311,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz#a11af19aa373d68d561f08e0a57242350ed0ec50" - integrity sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg== +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.20.7.tgz#d9c85589258539a22a901033853101a6198d4ef1" + integrity sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" + "@babel/plugin-proposal-optional-chaining" "^7.20.7" -"@babel/plugin-proposal-async-generator-functions@^7.20.1": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9" - integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g== +"@babel/plugin-proposal-async-generator-functions@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz#bfb7276d2d573cb67ba379984a2334e262ba5326" + integrity sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA== dependencies: "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-syntax-async-generators" "^7.8.4" @@ -386,13 +338,13 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-class-static-block@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" - integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== +"@babel/plugin-proposal-class-static-block@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz#77bdd66fb7b605f3a61302d224bdfacf5547977d" + integrity sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw== dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-class-static-block" "^7.14.5" "@babel/plugin-proposal-decorators@^7.21.0": @@ -430,12 +382,12 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-json-strings" "^7.8.3" -"@babel/plugin-proposal-logical-assignment-operators@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz#8148cbb350483bf6220af06fa6db3690e14b2e23" - integrity sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q== +"@babel/plugin-proposal-logical-assignment-operators@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz#dfbcaa8f7b4d37b51e8bfb46d94a5aea2bb89d83" + integrity sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": @@ -454,16 +406,16 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-numeric-separator" "^7.10.4" -"@babel/plugin-proposal-object-rest-spread@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d" - integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ== +"@babel/plugin-proposal-object-rest-spread@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz#aa662940ef425779c75534a5c41e9d936edc390a" + integrity sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg== dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" + "@babel/compat-data" "^7.20.5" + "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-parameters" "^7.20.7" "@babel/plugin-proposal-optional-catch-binding@^7.18.6": version "7.18.6" @@ -473,13 +425,13 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" -"@babel/plugin-proposal-optional-chaining@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz#e8e8fe0723f2563960e4bf5e9690933691915993" - integrity sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w== +"@babel/plugin-proposal-optional-chaining@^7.20.7", "@babel/plugin-proposal-optional-chaining@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz#886f5c8978deb7d30f678b2e24346b287234d3ea" + integrity sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-proposal-private-methods@^7.18.6": @@ -490,14 +442,14 @@ "@babel/helper-create-class-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-proposal-private-property-in-object@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" - integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== +"@babel/plugin-proposal-private-property-in-object@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz#19496bd9883dd83c23c7d7fc45dcd9ad02dfa1dc" + integrity sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.21.0" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": @@ -564,7 +516,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-syntax-import-meta@^7.8.3": +"@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== @@ -578,6 +530,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.0" +"@babel/plugin-syntax-jsx@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz#f264ed7bf40ffc9ec239edabc17a50c4f5b6fea2" + integrity sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ== + dependencies: + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -641,21 +600,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.19.0" -"@babel/plugin-transform-arrow-functions@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" - integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== +"@babel/plugin-transform-arrow-functions@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929" + integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.21.5" -"@babel/plugin-transform-async-to-generator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" - integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== +"@babel/plugin-transform-async-to-generator@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354" + integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q== dependencies: "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/helper-remap-async-to-generator" "^7.18.6" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-remap-async-to-generator" "^7.18.9" "@babel/plugin-transform-block-scoped-functions@^7.18.6": version "7.18.6" @@ -664,39 +623,40 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-block-scoping@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.2.tgz#f59b1767e6385c663fd0bce655db6ca9c8b236ed" - integrity sha512-y5V15+04ry69OV2wULmwhEA6jwSWXO1TwAtIwiPXcvHcoOQUqpyMVd2bDsQJMW8AurjulIyUV8kDqtjSwHy1uQ== +"@babel/plugin-transform-block-scoping@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02" + integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ== dependencies: "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-classes@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2" - integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g== +"@babel/plugin-transform-classes@^7.21.0": + version "7.21.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665" + integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ== dependencies: "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-compilation-targets" "^7.20.0" + "@babel/helper-compilation-targets" "^7.20.7" "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" + "@babel/helper-function-name" "^7.21.0" "@babel/helper-optimise-call-expression" "^7.18.6" "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-replace-supers" "^7.19.1" + "@babel/helper-replace-supers" "^7.20.7" "@babel/helper-split-export-declaration" "^7.18.6" globals "^11.1.0" -"@babel/plugin-transform-computed-properties@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz#2357a8224d402dad623caf6259b611e56aec746e" - integrity sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw== +"@babel/plugin-transform-computed-properties@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44" + integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/template" "^7.20.7" -"@babel/plugin-transform-destructuring@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792" - integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw== +"@babel/plugin-transform-destructuring@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401" + integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -723,12 +683,12 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-for-of@^7.18.8": - version "7.18.8" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz#6ef8a50b244eb6a0bdbad0c7c61877e4e30097c1" - integrity sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ== +"@babel/plugin-transform-for-of@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc" + integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-function-name@^7.18.9": version "7.18.9" @@ -753,31 +713,31 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-modules-amd@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd" - integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg== +"@babel/plugin-transform-modules-amd@^7.20.11": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a" + integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g== dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" -"@babel/plugin-transform-modules-commonjs@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c" - integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ== +"@babel/plugin-transform-modules-commonjs@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc" + integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ== dependencies: - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-simple-access" "^7.19.4" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-simple-access" "^7.21.5" -"@babel/plugin-transform-modules-systemjs@^7.19.6": - version "7.19.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d" - integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ== +"@babel/plugin-transform-modules-systemjs@^7.20.11": + version "7.20.11" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.20.11.tgz#467ec6bba6b6a50634eea61c9c232654d8a4696e" + integrity sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw== dependencies: "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-module-transforms" "^7.19.6" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/helper-validator-identifier" "^7.19.1" "@babel/plugin-transform-modules-umd@^7.18.6": @@ -788,13 +748,13 @@ "@babel/helper-module-transforms" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz#ec7455bab6cd8fb05c525a94876f435a48128888" - integrity sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw== +"@babel/plugin-transform-named-capturing-groups-regex@^7.20.5": + version "7.20.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8" + integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA== dependencies: - "@babel/helper-create-regexp-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" + "@babel/helper-create-regexp-features-plugin" "^7.20.5" + "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-transform-new-target@^7.18.6": version "7.18.6" @@ -811,10 +771,10 @@ "@babel/helper-plugin-utils" "^7.18.6" "@babel/helper-replace-supers" "^7.18.6" -"@babel/plugin-transform-parameters@^7.20.1": - version "7.20.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.3.tgz#7b3468d70c3c5b62e46be0a47b6045d8590fb748" - integrity sha512-oZg/Fpx0YDrj13KsLyO8I/CX3Zdw7z0O9qOd95SqcoIzuqy/WTGWvePeHAnZCN54SfdyjHcb1S30gc8zlzlHcA== +"@babel/plugin-transform-parameters@^7.20.7", "@babel/plugin-transform-parameters@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.21.3.tgz#18fc4e797cf6d6d972cb8c411dbe8a809fa157db" + integrity sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ== dependencies: "@babel/helper-plugin-utils" "^7.20.2" @@ -825,13 +785,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-regenerator@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" - integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== +"@babel/plugin-transform-regenerator@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e" + integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w== dependencies: - "@babel/helper-plugin-utils" "^7.18.6" - regenerator-transform "^0.15.0" + "@babel/helper-plugin-utils" "^7.21.5" + regenerator-transform "^0.15.1" "@babel/plugin-transform-reserved-words@^7.18.6": version "7.18.6" @@ -840,12 +800,12 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-runtime@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.0.tgz#2a884f29556d0a68cd3d152dcc9e6c71dfb6eee8" - integrity sha512-ReY6pxwSzEU0b3r2/T/VhqMKg/AkceBT19X0UptA3/tYi5Pe2eXgEUH+NNMC5nok6c6XQz5tyVTUpuezRfSMSg== +"@babel/plugin-transform-runtime@^7.21.4": + version "7.21.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.21.4.tgz#2e1da21ca597a7d01fc96b699b21d8d2023191aa" + integrity sha512-1J4dhrw1h1PqnNNpzwxQ2UBymJUF8KuPjAAnlLwZcGhHAIqUigFW7cdK6GHoB64ubY4qXQNYknoUeks4Wz7CUA== dependencies: - "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-module-imports" "^7.21.4" "@babel/helper-plugin-utils" "^7.20.2" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" @@ -859,13 +819,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-spread@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz#dd60b4620c2fec806d60cfaae364ec2188d593b6" - integrity sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w== +"@babel/plugin-transform-spread@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e" + integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw== dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-skip-transparent-expression-wrappers" "^7.18.9" + "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0" "@babel/plugin-transform-sticky-regex@^7.18.6": version "7.18.6" @@ -888,21 +848,22 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.9" -"@babel/plugin-transform-typescript@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz#f0956a153679e3b377ae5b7f0143427151e4c848" - integrity sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg== +"@babel/plugin-transform-typescript@^7.21.3": + version "7.21.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.3.tgz#316c5be579856ea890a57ebc5116c5d064658f2b" + integrity sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw== dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" "@babel/helper-create-class-features-plugin" "^7.21.0" "@babel/helper-plugin-utils" "^7.20.2" "@babel/plugin-syntax-typescript" "^7.20.0" -"@babel/plugin-transform-unicode-escapes@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz#1ecfb0eda83d09bbcb77c09970c2dd55832aa246" - integrity sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ== +"@babel/plugin-transform-unicode-escapes@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2" + integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg== dependencies: - "@babel/helper-plugin-utils" "^7.18.9" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/plugin-transform-unicode-regex@^7.18.6": version "7.18.6" @@ -912,31 +873,31 @@ "@babel/helper-create-regexp-features-plugin" "^7.18.6" "@babel/helper-plugin-utils" "^7.18.6" -"@babel/preset-env@^7.20.2": - version "7.20.2" - resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506" - integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg== +"@babel/preset-env@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.21.5.tgz#db2089d99efd2297716f018aeead815ac3decffb" + integrity sha512-wH00QnTTldTbf/IefEVyChtRdw5RJvODT/Vb4Vcxq1AZvtXj6T0YeX0cAcXhI6/BdGuiP3GcNIL4OQbI2DVNxg== dependencies: - "@babel/compat-data" "^7.20.1" - "@babel/helper-compilation-targets" "^7.20.0" - "@babel/helper-plugin-utils" "^7.20.2" - "@babel/helper-validator-option" "^7.18.6" + "@babel/compat-data" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-plugin-utils" "^7.21.5" + "@babel/helper-validator-option" "^7.21.0" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9" - "@babel/plugin-proposal-async-generator-functions" "^7.20.1" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.20.7" + "@babel/plugin-proposal-async-generator-functions" "^7.20.7" "@babel/plugin-proposal-class-properties" "^7.18.6" - "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.21.0" "@babel/plugin-proposal-dynamic-import" "^7.18.6" "@babel/plugin-proposal-export-namespace-from" "^7.18.9" "@babel/plugin-proposal-json-strings" "^7.18.6" - "@babel/plugin-proposal-logical-assignment-operators" "^7.18.9" + "@babel/plugin-proposal-logical-assignment-operators" "^7.20.7" "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" "@babel/plugin-proposal-numeric-separator" "^7.18.6" - "@babel/plugin-proposal-object-rest-spread" "^7.20.2" + "@babel/plugin-proposal-object-rest-spread" "^7.20.7" "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" - "@babel/plugin-proposal-optional-chaining" "^7.18.9" + "@babel/plugin-proposal-optional-chaining" "^7.21.0" "@babel/plugin-proposal-private-methods" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.21.0" "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-class-properties" "^7.12.13" @@ -944,6 +905,7 @@ "@babel/plugin-syntax-dynamic-import" "^7.8.3" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" "@babel/plugin-syntax-import-assertions" "^7.20.0" + "@babel/plugin-syntax-import-meta" "^7.10.4" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -953,40 +915,40 @@ "@babel/plugin-syntax-optional-chaining" "^7.8.3" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" "@babel/plugin-syntax-top-level-await" "^7.14.5" - "@babel/plugin-transform-arrow-functions" "^7.18.6" - "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.21.5" + "@babel/plugin-transform-async-to-generator" "^7.20.7" "@babel/plugin-transform-block-scoped-functions" "^7.18.6" - "@babel/plugin-transform-block-scoping" "^7.20.2" - "@babel/plugin-transform-classes" "^7.20.2" - "@babel/plugin-transform-computed-properties" "^7.18.9" - "@babel/plugin-transform-destructuring" "^7.20.2" + "@babel/plugin-transform-block-scoping" "^7.21.0" + "@babel/plugin-transform-classes" "^7.21.0" + "@babel/plugin-transform-computed-properties" "^7.21.5" + "@babel/plugin-transform-destructuring" "^7.21.3" "@babel/plugin-transform-dotall-regex" "^7.18.6" "@babel/plugin-transform-duplicate-keys" "^7.18.9" "@babel/plugin-transform-exponentiation-operator" "^7.18.6" - "@babel/plugin-transform-for-of" "^7.18.8" + "@babel/plugin-transform-for-of" "^7.21.5" "@babel/plugin-transform-function-name" "^7.18.9" "@babel/plugin-transform-literals" "^7.18.9" "@babel/plugin-transform-member-expression-literals" "^7.18.6" - "@babel/plugin-transform-modules-amd" "^7.19.6" - "@babel/plugin-transform-modules-commonjs" "^7.19.6" - "@babel/plugin-transform-modules-systemjs" "^7.19.6" + "@babel/plugin-transform-modules-amd" "^7.20.11" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" + "@babel/plugin-transform-modules-systemjs" "^7.20.11" "@babel/plugin-transform-modules-umd" "^7.18.6" - "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.20.5" "@babel/plugin-transform-new-target" "^7.18.6" "@babel/plugin-transform-object-super" "^7.18.6" - "@babel/plugin-transform-parameters" "^7.20.1" + "@babel/plugin-transform-parameters" "^7.21.3" "@babel/plugin-transform-property-literals" "^7.18.6" - "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.21.5" "@babel/plugin-transform-reserved-words" "^7.18.6" "@babel/plugin-transform-shorthand-properties" "^7.18.6" - "@babel/plugin-transform-spread" "^7.19.0" + "@babel/plugin-transform-spread" "^7.20.7" "@babel/plugin-transform-sticky-regex" "^7.18.6" "@babel/plugin-transform-template-literals" "^7.18.9" "@babel/plugin-transform-typeof-symbol" "^7.18.9" - "@babel/plugin-transform-unicode-escapes" "^7.18.10" + "@babel/plugin-transform-unicode-escapes" "^7.21.5" "@babel/plugin-transform-unicode-regex" "^7.18.6" "@babel/preset-modules" "^0.1.5" - "@babel/types" "^7.20.2" + "@babel/types" "^7.21.5" babel-plugin-polyfill-corejs2 "^0.3.3" babel-plugin-polyfill-corejs3 "^0.6.0" babel-plugin-polyfill-regenerator "^0.4.1" @@ -1004,14 +966,21 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" -"@babel/preset-typescript@^7.21.0": - version "7.21.0" - resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz#bcbbca513e8213691fe5d4b23d9251e01f00ebff" - integrity sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg== +"@babel/preset-typescript@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.21.5.tgz#68292c884b0e26070b4d66b202072d391358395f" + integrity sha512-iqe3sETat5EOrORXiQ6rWfoOg2y68Cs75B9wNxdPW4kixJxh7aXQE1KPdWLDniC24T/6dSnguF33W9j/ZZQcmA== dependencies: - "@babel/helper-plugin-utils" "^7.20.2" + "@babel/helper-plugin-utils" "^7.21.5" "@babel/helper-validator-option" "^7.21.0" - "@babel/plugin-transform-typescript" "^7.21.0" + "@babel/plugin-syntax-jsx" "^7.21.4" + "@babel/plugin-transform-modules-commonjs" "^7.21.5" + "@babel/plugin-transform-typescript" "^7.21.3" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime@^7.12.5", "@babel/runtime@^7.8.4": version "7.17.9" @@ -1029,28 +998,28 @@ "@babel/parser" "^7.20.7" "@babel/types" "^7.20.7" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.18.11", "@babel/traverse@^7.18.6", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.21.3": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.3.tgz#4747c5e7903d224be71f90788b06798331896f67" - integrity sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ== +"@babel/traverse@^7.1.0", "@babel/traverse@^7.18.11", "@babel/traverse@^7.20.7", "@babel/traverse@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.21.5.tgz#ad22361d352a5154b498299d523cf72998a4b133" + integrity sha512-AhQoI3YjWi6u/y/ntv7k48mcrCXmus0t79J9qPNlk/lAsFlCiJ047RmbfMOawySTHtywXhbXgpx/8nXMYd+oFw== dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.21.3" - "@babel/helper-environment-visitor" "^7.18.9" + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-environment-visitor" "^7.21.5" "@babel/helper-function-name" "^7.21.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.21.3" - "@babel/types" "^7.21.3" + "@babel/parser" "^7.21.5" + "@babel/types" "^7.21.5" debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.2", "@babel/types@^7.21.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": - version "7.21.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.3.tgz#4865a5357ce40f64e3400b0f3b737dc6d4f64d05" - integrity sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg== +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" + integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== dependencies: - "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-string-parser" "^7.21.5" "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0" @@ -1207,14 +1176,14 @@ resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403" integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ== -"@eslint/eslintrc@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.1.tgz#7888fe7ec8f21bc26d646dbd2c11cd776e21192d" - integrity sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw== +"@eslint/eslintrc@^2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.0.3.tgz#4910db5505f4d503f27774bf356e3704818a0331" + integrity sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.5.0" + espree "^9.5.2" globals "^13.19.0" ignore "^5.2.0" import-fresh "^3.2.1" @@ -1222,10 +1191,10 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.36.0": - version "8.36.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.36.0.tgz#9837f768c03a1e4a30bd304a64fb8844f0e72efe" - integrity sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg== +"@eslint/js@8.40.0": + version "8.40.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.40.0.tgz#3ba73359e11f5a7bd3e407f70b3528abfae69cec" + integrity sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA== "@humanwhocodes/config-array@^0.11.8": version "0.11.8" @@ -1575,10 +1544,10 @@ "@lit/reactive-element" "^1.0.0" "@open-wc/dedupe-mixin" "^1.3.0" -"@open-wc/testing-helpers@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@open-wc/testing-helpers/-/testing-helpers-2.2.0.tgz#cba3145d3b7ad3984183d340df68e83207733cef" - integrity sha512-4TGG5WLHeHyCgki5qsLTDZahA2gaJA0W8uc4ribNwR77vmYhEZMgfkgeCmaACoMQ9dfUCA2LOdnHtlxeR9Klyw== +"@open-wc/testing-helpers@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@open-wc/testing-helpers/-/testing-helpers-2.2.1.tgz#10ed75c33faec0ed68c76e027ebe8be262a36921" + integrity sha512-8zuJK7tUQYuXRIC/cVcPbAPOhtBJCe3Jfpk7im7WK0DIAXH9Q/ycB+yu3R8g4BQ31f/FdLjIFRbPZzIU75kkRg== dependencies: "@open-wc/scoped-elements" "^2.1.3" lit "^2.0.0" @@ -1589,7 +1558,7 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== -"@popperjs/core@^2.11.7", "@popperjs/core@^2.9.2": +"@popperjs/core@^2.11.7", "@popperjs/core@^2.9.0", "@popperjs/core@^2.9.2": version "2.11.7" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.7.tgz#ccab5c8f7dc557a52ca3288c10075c9ccd37fff7" integrity sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw== @@ -1620,10 +1589,10 @@ dependencies: "@sinonjs/commons" "^1.7.0" -"@testing-library/dom@^9.2.0": - version "9.2.0" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.2.0.tgz#0e1f45e956f2a16f471559c06edd8827c4832f04" - integrity sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA== +"@testing-library/dom@^9.3.0": + version "9.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-9.3.0.tgz#ed8ce10aa5e05eb6eaf0635b5b8975d889f66075" + integrity sha512-Dffe68pGwI6WlLRYR2I0piIkyole9cSBH5jGQKCGMRpHW5RHCqAUaqc2Kv0tUyd4dU4DLPKhJIjyKOnjv4tuUw== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -1925,10 +1894,10 @@ "@types/estree" "*" "@types/json-schema" "*" -"@types/estree@*", "@types/estree@^0.0.51": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== +"@types/estree@*", "@types/estree@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" + integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== "@types/geojson@*": version "7946.0.8" @@ -2053,15 +2022,15 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.56.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.56.0.tgz#e4fbb4d6dd8dab3e733485c1a44a02189ae75364" - integrity sha512-ZNW37Ccl3oMZkzxrYDUX4o7cnuPgU+YrcaYXzsRtLB16I1FR5SHMqga3zGsaSliZADCWo2v8qHWqAYIj8nWCCg== +"@typescript-eslint/eslint-plugin@^5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz#f156827610a3f8cefc56baeaa93cd4a5f32966b4" + integrity sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg== dependencies: "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.56.0" - "@typescript-eslint/type-utils" "5.56.0" - "@typescript-eslint/utils" "5.56.0" + "@typescript-eslint/scope-manager" "5.59.5" + "@typescript-eslint/type-utils" "5.59.5" + "@typescript-eslint/utils" "5.59.5" debug "^4.3.4" grapheme-splitter "^1.0.4" ignore "^5.2.0" @@ -2069,193 +2038,193 @@ semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/parser@^5.56.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.56.0.tgz#42eafb44b639ef1dbd54a3dbe628c446ca753ea6" - integrity sha512-sn1OZmBxUsgxMmR8a8U5QM/Wl+tyqlH//jTqCg8daTAmhAk26L2PFhcqPLlYBhYUJMZJK276qLXlHN3a83o2cg== +"@typescript-eslint/parser@^5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.59.5.tgz#63064f5eafbdbfb5f9dfbf5c4503cdf949852981" + integrity sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw== dependencies: - "@typescript-eslint/scope-manager" "5.56.0" - "@typescript-eslint/types" "5.56.0" - "@typescript-eslint/typescript-estree" "5.56.0" + "@typescript-eslint/scope-manager" "5.59.5" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/typescript-estree" "5.59.5" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.56.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz#62b4055088903b5254fa20403010e1c16d6ab725" - integrity sha512-jGYKyt+iBakD0SA5Ww8vFqGpoV2asSjwt60Gl6YcO8ksQ8s2HlUEyHBMSa38bdLopYqGf7EYQMUIGdT/Luw+sw== +"@typescript-eslint/scope-manager@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz#33ffc7e8663f42cfaac873de65ebf65d2bce674d" + integrity sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A== dependencies: - "@typescript-eslint/types" "5.56.0" - "@typescript-eslint/visitor-keys" "5.56.0" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/visitor-keys" "5.59.5" -"@typescript-eslint/type-utils@5.56.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz#e6f004a072f09c42e263dc50e98c70b41a509685" - integrity sha512-8WxgOgJjWRy6m4xg9KoSHPzBNZeQbGlQOH7l2QEhQID/+YseaFxg5J/DLwWSsi9Axj4e/cCiKx7PVzOq38tY4A== +"@typescript-eslint/type-utils@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz#485b0e2c5b923460bc2ea6b338c595343f06fc9b" + integrity sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg== dependencies: - "@typescript-eslint/typescript-estree" "5.56.0" - "@typescript-eslint/utils" "5.56.0" + "@typescript-eslint/typescript-estree" "5.59.5" + "@typescript-eslint/utils" "5.59.5" debug "^4.3.4" tsutils "^3.21.0" -"@typescript-eslint/types@5.56.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.56.0.tgz#b03f0bfd6fa2afff4e67c5795930aff398cbd834" - integrity sha512-JyAzbTJcIyhuUhogmiu+t79AkdnqgPUEsxMTMc/dCZczGMJQh1MK2wgrju++yMN6AWroVAy2jxyPcPr3SWCq5w== +"@typescript-eslint/types@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.59.5.tgz#e63c5952532306d97c6ea432cee0981f6d2258c7" + integrity sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w== -"@typescript-eslint/typescript-estree@5.56.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.56.0.tgz#48342aa2344649a03321e74cab9ccecb9af086c3" - integrity sha512-41CH/GncsLXOJi0jb74SnC7jVPWeVJ0pxQj8bOjH1h2O26jXN3YHKDT1ejkVz5YeTEQPeLCCRY0U2r68tfNOcg== +"@typescript-eslint/typescript-estree@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz#9b252ce55dd765e972a7a2f99233c439c5101e42" + integrity sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg== dependencies: - "@typescript-eslint/types" "5.56.0" - "@typescript-eslint/visitor-keys" "5.56.0" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/visitor-keys" "5.59.5" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" semver "^7.3.7" tsutils "^3.21.0" -"@typescript-eslint/utils@5.56.0", "@typescript-eslint/utils@^5.10.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.56.0.tgz#db64705409b9a15546053fb4deb2888b37df1f41" - integrity sha512-XhZDVdLnUJNtbzaJeDSCIYaM+Tgr59gZGbFuELgF7m0IY03PlciidS7UQNKLE0+WpUTn1GlycEr6Ivb/afjbhA== +"@typescript-eslint/utils@5.59.5", "@typescript-eslint/utils@^5.10.0": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.59.5.tgz#15b3eb619bb223302e60413adb0accd29c32bcae" + integrity sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@types/json-schema" "^7.0.9" "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.56.0" - "@typescript-eslint/types" "5.56.0" - "@typescript-eslint/typescript-estree" "5.56.0" + "@typescript-eslint/scope-manager" "5.59.5" + "@typescript-eslint/types" "5.59.5" + "@typescript-eslint/typescript-estree" "5.59.5" eslint-scope "^5.1.1" semver "^7.3.7" -"@typescript-eslint/visitor-keys@5.56.0": - version "5.56.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz#f19eb297d972417eb13cb69b35b3213e13cc214f" - integrity sha512-1mFdED7u5bZpX6Xxf5N9U2c18sb+8EvU3tyOIj6LQZ5OOvnmj8BVeNNP603OFPm5KkS1a7IvCIcwrdHXaEMG/Q== +"@typescript-eslint/visitor-keys@5.59.5": + version "5.59.5" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz#ba5b8d6791a13cf9fea6716af1e7626434b29b9b" + integrity sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA== dependencies: - "@typescript-eslint/types" "5.56.0" + "@typescript-eslint/types" "5.59.5" eslint-visitor-keys "^3.3.0" -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== +"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c" + integrity sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ== dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-numbers" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== +"@webassemblyjs/floating-point-hex-parser@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz#e85dfdb01cad16b812ff166b96806c050555f1b4" + integrity sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ== -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== +"@webassemblyjs/helper-api-error@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz#1e82fa7958c681ddcf4eabef756ce09d49d442d1" + integrity sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA== -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== +"@webassemblyjs/helper-buffer@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz#91381652ea95bb38bbfd270702351c0c89d69fba" + integrity sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg== -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== +"@webassemblyjs/helper-numbers@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz#23380c910d56764957292839006fecbe05e135a9" + integrity sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/floating-point-hex-parser" "1.11.5" + "@webassemblyjs/helper-api-error" "1.11.5" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== +"@webassemblyjs/helper-wasm-bytecode@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz#e258a25251bc69a52ef817da3001863cc1c24b9f" + integrity sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA== -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== +"@webassemblyjs/helper-wasm-section@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz#966e855a6fae04d5570ad4ec87fbcf29b42ba78e" + integrity sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA== dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== +"@webassemblyjs/ieee754@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz#b2db1b33ce9c91e34236194c2b5cba9b25ca9d60" + integrity sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== +"@webassemblyjs/leb128@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.5.tgz#482e44d26b6b949edf042a8525a66c649e38935a" + integrity sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" +"@webassemblyjs/utf8@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.5.tgz#83bef94856e399f3740e8df9f63bc47a987eae1a" + integrity sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ== + +"@webassemblyjs/wasm-edit@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz#93ee10a08037657e21c70de31c47fdad6b522b2d" + integrity sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/helper-wasm-section" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/wasm-opt" "1.11.5" + "@webassemblyjs/wasm-parser" "1.11.5" + "@webassemblyjs/wast-printer" "1.11.5" + +"@webassemblyjs/wasm-gen@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz#ceb1c82b40bf0cf67a492c53381916756ef7f0b1" + integrity sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/ieee754" "1.11.5" + "@webassemblyjs/leb128" "1.11.5" + "@webassemblyjs/utf8" "1.11.5" + +"@webassemblyjs/wasm-opt@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz#b52bac29681fa62487e16d3bb7f0633d5e62ca0a" + integrity sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-buffer" "1.11.5" + "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/wasm-parser" "1.11.5" + +"@webassemblyjs/wasm-parser@1.11.5", "@webassemblyjs/wasm-parser@^1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz#7ba0697ca74c860ea13e3ba226b29617046982e2" + integrity sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew== + dependencies: + "@webassemblyjs/ast" "1.11.5" + "@webassemblyjs/helper-api-error" "1.11.5" + "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/ieee754" "1.11.5" + "@webassemblyjs/leb128" "1.11.5" + "@webassemblyjs/utf8" "1.11.5" + +"@webassemblyjs/wast-printer@1.11.5": + version "1.11.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz#7a5e9689043f3eca82d544d7be7a8e6373a6fa98" + integrity sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA== + dependencies: + "@webassemblyjs/ast" "1.11.5" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^1.1.1": @@ -3016,10 +2985,10 @@ core-js-compat@^3.25.1: dependencies: browserslist "^4.21.4" -core-js@^3.29.1: - version "3.29.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.29.1.tgz#40ff3b41588b091aaed19ca1aa5cb111803fa9a6" - integrity sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw== +core-js@^3.30.2: + version "3.30.2" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.2.tgz#6528abfda65e5ad728143ea23f7a14f0dcf503fc" + integrity sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg== cosmiconfig@^7.0.0, cosmiconfig@^7.1.0: version "7.1.0" @@ -3304,10 +3273,10 @@ d3-zoom@3: d3-selection "2 - 3" d3-transition "2 - 3" -d3@^7.8.3: - version "7.8.3" - resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.3.tgz#b6c862d16368e15bf764c89e248a300a56f2ab40" - integrity sha512-cAa866AkPXtt4IzRx6nzGf50uerq6VYks7p/tTH94be4QfhUkyTfJfaxXGPOB5ZRVUZmUV1wcM1dism/Ua0lCw== +d3@^7.8.4: + version "7.8.4" + resolved "https://registry.yarnpkg.com/d3/-/d3-7.8.4.tgz#e35d45800e4068cab07e59e5d883a4bb42ab217f" + integrity sha512-q2WHStdhiBtD8DMmhDPyJmXUxr6VWRngKyiJ5EfXMxPw+tqT6BhNjhJZ4w3BHsNm3QoVfZLY8Orq/qPFczwKRA== dependencies: d3-array "3" d3-axis "3" @@ -3561,10 +3530,10 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.10.0: - version "5.10.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" - integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== +enhanced-resolve@^5.14.0: + version "5.14.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz#0b6c676c8a3266c99fa281e4433a706f5c0c61c4" + integrity sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -3669,10 +3638,10 @@ es-get-iterator@^1.1.2: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" -es-module-lexer@^0.9.0: - version "0.9.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" - integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== +es-module-lexer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527" + integrity sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg== es-set-tostringtag@^2.0.1: version "2.0.1" @@ -3746,10 +3715,10 @@ eslint-plugin-jest@^27.2.1: dependencies: "@typescript-eslint/utils" "^5.10.0" -eslint-plugin-lit@^1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-lit/-/eslint-plugin-lit-1.8.2.tgz#e4ba34f641e7ffdde3c6004313a99dafd1945a43" - integrity sha512-4mOGcSRNEPMh7AN2F7Iy6no36nuFgyYOsnTRhFw1k8xyy1Zm6QOp788ywDvJqy+eelFbLPBhq20Qr55a887Dmw== +eslint-plugin-lit@^1.8.3: + version "1.8.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-lit/-/eslint-plugin-lit-1.8.3.tgz#f532c2947b503da62efe2d72372ad3df09b8afd4" + integrity sha512-wmeYfBnWPUChbdZagOhG519gaWz9Q7OGT/nCx3YVHuCCrW9q9u0p/IQueQeoaMojUqOSgM/22oSDOaBruYGqag== dependencies: parse5 "^6.0.1" parse5-htmlparser2-tree-adapter "^6.0.1" @@ -3760,10 +3729,10 @@ eslint-plugin-no-jquery@^2.7.0: resolved "https://registry.yarnpkg.com/eslint-plugin-no-jquery/-/eslint-plugin-no-jquery-2.7.0.tgz#855f5631cf5b8e25b930cf6f06e02dd81f132e72" integrity sha512-Aeg7dA6GTH1AcWLlBtWNzOU9efK5KpNi7b0EhBO0o0M+awyzguUUo8gF6hXGjQ9n5h8/uRtYv9zOqQkeC5CG0w== -eslint-plugin-wc@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-wc/-/eslint-plugin-wc-1.4.0.tgz#5996b0c4d7183d0d51765e52bba886cd4aa70741" - integrity sha512-AmoKhJyBNcS3I+dbS/JTmRSq4REUvQ/JJCeWJezlK8gqTsdr5JD+EAvHldH/tVvU+l6qR2Tykga5hTINP9zS8A== +eslint-plugin-wc@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-wc/-/eslint-plugin-wc-1.5.0.tgz#7b8b1f126947c2a2cfd3188c593dde1f468affe9" + integrity sha512-KFSfiHDol/LeV7U6IX8GdgpGf/s3wG8FTG120Rml/hGNB/DkCuGYQhlf0VgdBdf7gweem8Nlsh5o64HNdj+qPA== dependencies: is-valid-element-name "^1.0.0" js-levenshtein-esm "^1.2.0" @@ -3776,28 +3745,28 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== +eslint-scope@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b" + integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" + integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== -eslint@^8.36.0: - version "8.36.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.36.0.tgz#1bd72202200a5492f91803b113fb8a83b11285cf" - integrity sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw== +eslint@^8.40.0: + version "8.40.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.40.0.tgz#a564cd0099f38542c4e9a2f630fa45bf33bc42a4" + integrity sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ== dependencies: "@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/regexpp" "^4.4.0" - "@eslint/eslintrc" "^2.0.1" - "@eslint/js" "8.36.0" + "@eslint/eslintrc" "^2.0.3" + "@eslint/js" "8.40.0" "@humanwhocodes/config-array" "^0.11.8" "@humanwhocodes/module-importer" "^1.0.1" "@nodelib/fs.walk" "^1.2.8" @@ -3807,9 +3776,9 @@ eslint@^8.36.0: debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-visitor-keys "^3.3.0" - espree "^9.5.0" + eslint-scope "^7.2.0" + eslint-visitor-keys "^3.4.1" + espree "^9.5.2" esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" @@ -3835,14 +3804,14 @@ eslint@^8.36.0: strip-json-comments "^3.1.0" text-table "^0.2.0" -espree@^9.5.0: - version "9.5.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.0.tgz#3646d4e3f58907464edba852fa047e6a27bdf113" - integrity sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw== +espree@^9.5.2: + version "9.5.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.5.2.tgz#e994e7dc33a082a7a82dceaf12883a829353215b" + integrity sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw== dependencies: acorn "^8.8.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.1" esprima@^4.0.0, esprima@^4.0.1: version "4.0.1" @@ -4366,16 +4335,11 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.15: +graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - grapheme-splitter@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" @@ -5245,10 +5209,10 @@ jest-jasmine2@^26.6.3: pretty-format "^26.6.2" throat "^5.0.0" -jest-junit@^15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-15.0.0.tgz#a47544ab42e9f8fe7ada56306c218e09e52bd690" - integrity sha512-Z5sVX0Ag3HZdMUnD5DFlG+1gciIFSy7yIVPhOdGUi8YJaI9iLvvBb530gtQL2CHmv0JJeiwRZenr0VrSR7frvg== +jest-junit@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/jest-junit/-/jest-junit-16.0.0.tgz#d838e8c561cf9fdd7eb54f63020777eee4136785" + integrity sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ== dependencies: mkdirp "^1.0.4" strip-ansi "^6.0.1" @@ -5492,10 +5456,10 @@ jest@^26.6.3: import-local "^3.0.2" jest-cli "^26.6.3" -jquery@^3.6.4: - version "3.6.4" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.6.4.tgz#ba065c188142100be4833699852bf7c24dc0252f" - integrity sha512-v28EW9DWDFpzcD9O5iyJXg3R3+q+mET5JhnjJzQUZMHOv67bpSIHq81GEYpPNZHG+XXHsfSme3nxp/hndKEcsQ== +jquery@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.7.0.tgz#fe2c01a05da500709006d8790fe21c8a39d75612" + integrity sha512-umpJ0/k8X0MvD1ds0P9SfowREz2LenHsQaxSohMZ5OMNEU2r0tf8pdeEFTHMFxWVxKNyU9rTtK3CWzUCTKJUeQ== js-levenshtein-esm@^1.2.0: version "1.2.0" @@ -5671,10 +5635,10 @@ lit-html@^2.0.0, lit-html@^2.7.0: dependencies: "@types/trusted-types" "^2.0.2" -lit@2.7.0, lit@^2.0.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/lit/-/lit-2.7.0.tgz#94242caa20f7b9e60d49cc0b843e4a694c4af3bb" - integrity sha512-qSy2BAVA+OiWtNptP404egcC/izDdNRw6iHGIbUmkZtbMJvPKfNsaoKrNs8Zmsbjmv5ZX2tur1l9TfzkSWWT4g== +lit@2.7.4, lit@^2.0.0: + version "2.7.4" + resolved "https://registry.yarnpkg.com/lit/-/lit-2.7.4.tgz#ca63d27fda178dbffae0faf2c882b9910e40842c" + integrity sha512-cgD7xrZoYr21mbrkZIuIrj98YTMw/snJPg52deWVV4A8icLyNHI3bF70xsJeAgwTuiq5Kkd+ZR8gybSJDCPB7g== dependencies: "@lit/reactive-element" "^1.6.0" lit-element "^3.3.0" @@ -6531,6 +6495,13 @@ regenerate-unicode-properties@^10.0.1: dependencies: regenerate "^1.4.2" +regenerate-unicode-properties@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" + integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ== + dependencies: + regenerate "^1.4.2" + regenerate@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" @@ -6541,10 +6512,10 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== -regenerator-transform@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" - integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== +regenerator-transform@^0.15.1: + version "0.15.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56" + integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg== dependencies: "@babel/runtime" "^7.8.4" @@ -6577,6 +6548,18 @@ regexpu-core@^5.1.0: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.0.0" +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + regjsgen@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" @@ -6589,6 +6572,13 @@ regjsparser@^0.8.2: dependencies: jsesc "~0.5.0" +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + release-zalgo@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" @@ -6752,10 +6742,10 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" -sass@^1.60.0: - version "1.60.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.60.0.tgz#657f0c23a302ac494b09a5ba8497b739fb5b5a81" - integrity sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ== +sass@^1.62.1: + version "1.62.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.62.1.tgz#caa8d6bf098935bc92fc73fa169fb3790cacd029" + integrity sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A== dependencies: chokidar ">=3.0.0 <4.0.0" immutable "^4.0.0" @@ -6768,10 +6758,10 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== +schema-utils@^3.1.1, schema-utils@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99" + integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg== dependencies: "@types/json-schema" "^7.0.8" ajv "^6.12.5" @@ -6809,10 +6799,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -serialize-javascript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== +serialize-javascript@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c" + integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w== dependencies: randombytes "^2.1.0" @@ -7323,21 +7313,21 @@ terminal-link@^2.0.0: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser-webpack-plugin@^5.1.3: - version "5.3.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.1.tgz#0320dcc270ad5372c1e8993fabbd927929773e54" - integrity sha512-GvlZdT6wPQKbDNW/GDQzZFg/j4vKU96yl2q6mcUkzKOgW4gwf1Z8cZToUCrz31XHlPWH8MVb1r2tFtdDtTGJ7g== +terser-webpack-plugin@^5.3.7: + version "5.3.7" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.7.tgz#ef760632d24991760f339fe9290deb936ad1ffc7" + integrity sha512-AfKwIktyP7Cu50xNjXF/6Qb5lBNzYaWpU6YfoX3uZicTx0zTy0stDDCsvjDapKsSDvOeWo5MEq4TmdBy2cNoHw== dependencies: + "@jridgewell/trace-mapping" "^0.3.17" jest-worker "^27.4.5" schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - terser "^5.7.2" + serialize-javascript "^6.0.1" + terser "^5.16.5" -terser@^5.7.2: - version "5.14.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" - integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== +terser@^5.16.5: + version "5.16.9" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.9.tgz#7a28cb178e330c484369886f2afd623d9847495f" + integrity sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg== dependencies: "@jridgewell/source-map" "^0.3.2" acorn "^8.5.0" @@ -7373,6 +7363,13 @@ tiny-emitter@^2.0.0: resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== +tippy.js@^6.3.7: + version "6.3.7" + resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.7.tgz#8ccfb651d642010ed9a32ff29b0e9e19c5b8c61c" + integrity sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ== + dependencies: + "@popperjs/core" "^2.9.0" + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -7562,6 +7559,11 @@ unicode-match-property-value-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + unicode-property-aliases-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" @@ -7745,22 +7747,22 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@^5.76.3: - version "5.76.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.3.tgz#dffdc72c8950e5b032fddad9c4452e7787d2f489" - integrity sha512-18Qv7uGPU8b2vqGeEEObnfICyw2g39CHlDEK4I7NK13LOur1d0HGmGNKGT58Eluwddpn3oEejwvBPoP4M7/KSA== +webpack@^5.82.1: + version "5.82.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.82.1.tgz#8f38c78e53467556e8a89054ebd3ef6e9f67dbab" + integrity sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw== dependencies: "@types/eslint-scope" "^3.7.3" - "@types/estree" "^0.0.51" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" + "@types/estree" "^1.0.0" + "@webassemblyjs/ast" "^1.11.5" + "@webassemblyjs/wasm-edit" "^1.11.5" + "@webassemblyjs/wasm-parser" "^1.11.5" acorn "^8.7.1" acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.10.0" - es-module-lexer "^0.9.0" + enhanced-resolve "^5.14.0" + es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" @@ -7769,9 +7771,9 @@ webpack@^5.76.3: loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" - schema-utils "^3.1.0" + schema-utils "^3.1.2" tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" + terser-webpack-plugin "^5.3.7" watchpack "^2.4.0" webpack-sources "^3.2.3"