mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-29 10:12:34 +00:00
feat: multiple UX improvements to labels (#7358)
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Co-authored-by: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -74,3 +74,5 @@ yalc.lock
|
||||
/yarn-error.log
|
||||
yarn-debug.log*
|
||||
.yarn-integrity
|
||||
|
||||
/storybook-static
|
||||
25
app/javascript/dashboard/components/base/Hotkey.vue
Normal file
25
app/javascript/dashboard/components/base/Hotkey.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<kbd class="hotkey">
|
||||
<slot />
|
||||
</kbd>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
kbd.hotkey {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
justify-content: center;
|
||||
padding: var(--space-micro);
|
||||
min-width: var(--space-normal);
|
||||
font-size: var(--font-size-micro);
|
||||
border-radius: var(--space-smaller);
|
||||
background-color: var(--color-background);
|
||||
border: 1px solid var(--color-border);
|
||||
text-transform: uppercase;
|
||||
color: var(--color-body);
|
||||
letter-spacing: var(--space-micro);
|
||||
line-height: var(--font-size-micro);
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,7 @@
|
||||
v-if="showSearchDropdownLabel"
|
||||
:account-labels="allLabels"
|
||||
:selected-labels="selectedLabels"
|
||||
:allow-creation="isAdmin"
|
||||
@add="addItem"
|
||||
@remove="removeItem"
|
||||
/>
|
||||
@@ -30,8 +31,15 @@
|
||||
|
||||
<script>
|
||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||
import {
|
||||
buildHotKeys,
|
||||
isEscape,
|
||||
isActiveElementTypeable,
|
||||
} from 'shared/helpers/KeyboardHelpers';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -39,7 +47,7 @@ export default {
|
||||
LabelDropdown,
|
||||
},
|
||||
|
||||
mixins: [clickaway],
|
||||
mixins: [clickaway, adminMixin, eventListenerMixins],
|
||||
|
||||
props: {
|
||||
allLabels: {
|
||||
@@ -80,6 +88,18 @@ export default {
|
||||
closeDropdownLabel() {
|
||||
this.showSearchDropdownLabel = false;
|
||||
},
|
||||
|
||||
handleKeyEvents(e) {
|
||||
const keyPattern = buildHotKeys(e);
|
||||
|
||||
if (keyPattern === 'l' && !isActiveElementTypeable(e)) {
|
||||
this.toggleLabels();
|
||||
e.preventDefault();
|
||||
} else if (isEscape(e) && this.showSearchDropdownLabel) {
|
||||
this.closeDropdownLabel();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -33,7 +33,8 @@
|
||||
"LABEL_SELECT": {
|
||||
"TITLE": "Add Labels",
|
||||
"PLACEHOLDER": "Search labels",
|
||||
"NO_RESULT": "No labels found"
|
||||
"NO_RESULT": "No labels found",
|
||||
"CREATE_LABEL": "Create new label"
|
||||
}
|
||||
},
|
||||
"MERGE_CONTACT": "Merge contact",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
v-if="showSearchDropdownLabel"
|
||||
:account-labels="accountLabels"
|
||||
:selected-labels="savedLabels"
|
||||
:allow-creation="isAdmin"
|
||||
@add="addLabelToConversation"
|
||||
@remove="removeLabelFromConversation"
|
||||
/>
|
||||
@@ -47,7 +48,14 @@ import Spinner from 'shared/components/Spinner';
|
||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import conversationLabelMixin from 'dashboard/mixins/conversation/labelMixin';
|
||||
import {
|
||||
buildHotKeys,
|
||||
isEscape,
|
||||
isActiveElementTypeable,
|
||||
} from 'shared/helpers/KeyboardHelpers';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -56,7 +64,7 @@ export default {
|
||||
AddLabel,
|
||||
},
|
||||
|
||||
mixins: [clickaway, conversationLabelMixin],
|
||||
mixins: [clickaway, conversationLabelMixin, adminMixin, eventListenerMixins],
|
||||
props: {
|
||||
conversationId: {
|
||||
type: Number,
|
||||
@@ -84,6 +92,17 @@ export default {
|
||||
closeDropdownLabel() {
|
||||
this.showSearchDropdownLabel = false;
|
||||
},
|
||||
handleKeyEvents(e) {
|
||||
const keyPattern = buildHotKeys(e);
|
||||
|
||||
if (keyPattern === 'l' && !isActiveElementTypeable(e)) {
|
||||
this.toggleLabels();
|
||||
e.preventDefault();
|
||||
} else if (isEscape(e) && this.showSearchDropdownLabel) {
|
||||
this.closeDropdownLabel();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -65,6 +65,12 @@ import { getRandomColor } from 'dashboard/helper/labelColor';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin, validationMixin],
|
||||
props: {
|
||||
prefillTitle: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
color: '#000',
|
||||
@@ -81,6 +87,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
this.color = getRandomColor();
|
||||
this.title = this.prefillTitle.toLowerCase();
|
||||
},
|
||||
methods: {
|
||||
onClose() {
|
||||
|
||||
@@ -45,7 +45,10 @@ export const actions = {
|
||||
commit(types.SET_LABEL_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const response = await LabelsAPI.get(true);
|
||||
commit(types.SET_LABELS, response.data.payload);
|
||||
const sortedLabels = response.data.payload.sort((a, b) =>
|
||||
a.title.localeCompare(b.title)
|
||||
);
|
||||
commit(types.SET_LABELS, sortedLabels);
|
||||
} catch (error) {
|
||||
// Ignore error
|
||||
} finally {
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<div class="dropdown-search-wrap">
|
||||
<h4 class="text-block-title">
|
||||
{{ $t('CONTACT_PANEL.LABELS.LABEL_SELECT.TITLE') }}
|
||||
</h4>
|
||||
<div class="dropdown-title-container">
|
||||
<h4 class="text-block-title">
|
||||
{{ $t('CONTACT_PANEL.LABELS.LABEL_SELECT.TITLE') }}
|
||||
</h4>
|
||||
<hotkey>L</hotkey>
|
||||
</div>
|
||||
<div class="search-wrap">
|
||||
<input
|
||||
ref="searchbar"
|
||||
@@ -28,6 +31,31 @@
|
||||
<div v-if="noResult" class="no-result">
|
||||
{{ $t('CONTACT_PANEL.LABELS.LABEL_SELECT.NO_RESULT') }}
|
||||
</div>
|
||||
<div v-if="allowCreation && shouldShowCreate" class="new-label">
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
icon="add"
|
||||
is-expanded
|
||||
class="button-new-label"
|
||||
:is-disabled="hasExactMatchInResults"
|
||||
@click="showCreateModal"
|
||||
>
|
||||
{{ createLabelPlaceholder }}
|
||||
{{ parsedSearch }}
|
||||
</woot-button>
|
||||
|
||||
<woot-modal
|
||||
:show.sync="createModalVisible"
|
||||
:on-close="hideCreateModal"
|
||||
>
|
||||
<add-label-modal
|
||||
:prefill-title="parsedSearch"
|
||||
@close="hideCreateModal"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -35,9 +63,16 @@
|
||||
|
||||
<script>
|
||||
import LabelDropdownItem from './LabelDropdownItem';
|
||||
import Hotkey from 'dashboard/components/base/Hotkey';
|
||||
import AddLabelModal from 'dashboard/routes/dashboard/settings/labels/AddLabel';
|
||||
import { picoSearch } from '@scmmishra/pico-search';
|
||||
import { sanitizeLabel } from 'shared/helpers/sanitizeData';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
LabelDropdownItem,
|
||||
AddLabelModal,
|
||||
Hotkey,
|
||||
},
|
||||
|
||||
props: {
|
||||
@@ -49,23 +84,49 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
allowCreation: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
createModalVisible: false,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
createLabelPlaceholder() {
|
||||
const label = this.$t('CONTACT_PANEL.LABELS.LABEL_SELECT.CREATE_LABEL');
|
||||
return this.search ? `${label}:` : label;
|
||||
},
|
||||
|
||||
filteredActiveLabels() {
|
||||
return this.accountLabels.filter(label => {
|
||||
return label.title.toLowerCase().includes(this.search.toLowerCase());
|
||||
if (!this.search) return this.accountLabels;
|
||||
|
||||
return picoSearch(this.accountLabels, this.search, ['title'], {
|
||||
threshold: 0.9,
|
||||
});
|
||||
},
|
||||
|
||||
noResult() {
|
||||
return this.filteredActiveLabels.length === 0 && this.search !== '';
|
||||
return this.filteredActiveLabels.length === 0;
|
||||
},
|
||||
|
||||
hasExactMatchInResults() {
|
||||
return this.filteredActiveLabels.some(
|
||||
label => label.title === this.search
|
||||
);
|
||||
},
|
||||
|
||||
shouldShowCreate() {
|
||||
return this.allowCreation && this.filteredActiveLabels.length < 3;
|
||||
},
|
||||
|
||||
parsedSearch() {
|
||||
return sanitizeLabel(this.search);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -97,11 +158,35 @@ export default {
|
||||
this.onAdd(label);
|
||||
}
|
||||
},
|
||||
|
||||
showCreateModal() {
|
||||
this.createModalVisible = true;
|
||||
},
|
||||
|
||||
hideCreateModal() {
|
||||
this.createModalVisible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dropdown-title-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-smaller);
|
||||
|
||||
.text-block-title {
|
||||
flex-grow: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hotkey {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-search-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -124,7 +209,7 @@ export default {
|
||||
}
|
||||
|
||||
input:focus {
|
||||
border: 1px solid var(--w-500);
|
||||
outline: 1px solid var(--color-border-dark);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,9 +228,30 @@ export default {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--s-700);
|
||||
padding: var(--space-smaller) var(--space-one);
|
||||
padding: var(--space-normal) var(--space-one);
|
||||
font-weight: var(--font-weight-medium);
|
||||
font-size: var(--font-size-small);
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.new-label {
|
||||
display: flex;
|
||||
padding-top: var(--space-smaller);
|
||||
border-top: 1px solid var(--s-100);
|
||||
|
||||
.button-new-label {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search-term {
|
||||
color: var(--s-700);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,6 +106,11 @@ export const hasPressedCommandPlusKKey = e => {
|
||||
return e.metaKey && e.keyCode === 75;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a string representation of the hotkey pattern based on the provided event object.
|
||||
* @param {KeyboardEvent} e - The keyboard event object.
|
||||
* @returns {string} - The hotkey pattern string.
|
||||
*/
|
||||
export const buildHotKeys = e => {
|
||||
const key = e.key.toLowerCase();
|
||||
if (['shift', 'meta', 'alt', 'control'].includes(key)) {
|
||||
@@ -127,3 +132,30 @@ export const buildHotKeys = e => {
|
||||
hotKeyPattern += key;
|
||||
return hotKeyPattern;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines whether the active element is typeable.
|
||||
*
|
||||
* @param {KeyboardEvent} e - The keyboard event object.
|
||||
* @returns {boolean} `true` if the active element is typeable, `false` otherwise.
|
||||
*
|
||||
* @example
|
||||
* document.addEventListener('keydown', e => {
|
||||
* if (isActiveElementTypeable(e)) {
|
||||
* handleTypeableElement(e);
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
export const isActiveElementTypeable = e => {
|
||||
/** @type {HTMLElement | null} */
|
||||
// @ts-ignore
|
||||
const activeElement = e.target || document.activeElement;
|
||||
|
||||
return !!(
|
||||
activeElement?.tagName === 'INPUT' ||
|
||||
activeElement?.tagName === 'NINJA-KEYS' ||
|
||||
activeElement?.tagName === 'TEXTAREA' ||
|
||||
activeElement?.contentEditable === 'true' ||
|
||||
activeElement?.className?.includes('ProseMirror')
|
||||
);
|
||||
};
|
||||
|
||||
22
app/javascript/shared/helpers/sanitizeData.js
Normal file
22
app/javascript/shared/helpers/sanitizeData.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export const labelSanitizePattern = /[^a-zA-Z0-9_-]/g;
|
||||
export const spacesPattern = /\s+/g;
|
||||
|
||||
/**
|
||||
* Sanitizes a label by removing unwanted characters and replacing spaces with hyphens.
|
||||
*
|
||||
* @param {string | undefined | null} label - The label to sanitize.
|
||||
* @returns {string} The sanitized label.
|
||||
*
|
||||
* @example
|
||||
* const label = 'My Label 123';
|
||||
* const sanitizedLabel = sanitizeLabel(label); // 'my-label-123'
|
||||
*/
|
||||
export const sanitizeLabel = (label = '') => {
|
||||
if (!label) return '';
|
||||
|
||||
return label
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(spacesPattern, '-')
|
||||
.replace(labelSanitizePattern, '');
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
hasPressedShift,
|
||||
hasPressedCommand,
|
||||
buildHotKeys,
|
||||
isActiveElementTypeable,
|
||||
} from '../KeyboardHelpers';
|
||||
|
||||
describe('#KeyboardHelpers', () => {
|
||||
@@ -39,3 +40,37 @@ describe('#KeyboardHelpers', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isActiveElementTypeable', () => {
|
||||
it('should return true if the active element is an input element', () => {
|
||||
const event = { target: document.createElement('input') };
|
||||
const result = isActiveElementTypeable(event);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the active element is a textarea element', () => {
|
||||
const event = { target: document.createElement('textarea') };
|
||||
const result = isActiveElementTypeable(event);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the active element is a contentEditable element', () => {
|
||||
const element = document.createElement('div');
|
||||
element.contentEditable = 'true';
|
||||
const event = { target: element };
|
||||
const result = isActiveElementTypeable(event);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the active element is not typeable', () => {
|
||||
const event = { target: document.createElement('div') };
|
||||
const result = isActiveElementTypeable(event);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the active element is null', () => {
|
||||
const event = { target: null };
|
||||
const result = isActiveElementTypeable(event);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
44
app/javascript/shared/helpers/specs/sanitizeData.spec.js
Normal file
44
app/javascript/shared/helpers/specs/sanitizeData.spec.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import { sanitizeLabel } from '../sanitizeData';
|
||||
|
||||
describe('sanitizeLabel', () => {
|
||||
it('should return an empty string when given an empty string', () => {
|
||||
const label = '';
|
||||
const sanitizedLabel = sanitizeLabel(label);
|
||||
expect(sanitizedLabel).toEqual('');
|
||||
});
|
||||
|
||||
it('should remove leading and trailing whitespace', () => {
|
||||
const label = ' My Label ';
|
||||
const sanitizedLabel = sanitizeLabel(label);
|
||||
expect(sanitizedLabel).toEqual('my-label');
|
||||
});
|
||||
|
||||
it('should convert all characters to lowercase', () => {
|
||||
const label = 'My Label';
|
||||
const sanitizedLabel = sanitizeLabel(label);
|
||||
expect(sanitizedLabel).toEqual('my-label');
|
||||
});
|
||||
|
||||
it('should replace spaces with hyphens', () => {
|
||||
const label = 'My Label 123';
|
||||
const sanitizedLabel = sanitizeLabel(label);
|
||||
expect(sanitizedLabel).toEqual('my-label-123');
|
||||
});
|
||||
|
||||
it('should remove any characters that are not alphanumeric, underscore, or hyphen', () => {
|
||||
const label = 'My_Label!123';
|
||||
const sanitizedLabel = sanitizeLabel(label);
|
||||
expect(sanitizedLabel).toEqual('my_label123');
|
||||
});
|
||||
|
||||
it('should handle null and undefined input', () => {
|
||||
const nullLabel = null;
|
||||
const undefinedLabel = undefined;
|
||||
|
||||
// @ts-ignore - intentionally passing null and undefined to test
|
||||
const sanitizedNullLabel = sanitizeLabel(nullLabel);
|
||||
const sanitizedUndefinedLabel = sanitizeLabel(undefinedLabel);
|
||||
expect(sanitizedNullLabel).toEqual('');
|
||||
expect(sanitizedUndefinedLabel).toEqual('');
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isEscape } from '../helpers/KeyboardHelpers';
|
||||
import { isActiveElementTypeable, isEscape } from '../helpers/KeyboardHelpers';
|
||||
|
||||
export default {
|
||||
mounted() {
|
||||
@@ -9,13 +9,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onKeyDownHandler(e) {
|
||||
const isEventFromAnInputBox =
|
||||
e.target?.tagName === 'INPUT' || e.target?.tagName === 'TEXTAREA';
|
||||
const isEventFromProseMirror = e.target?.className?.includes(
|
||||
'ProseMirror'
|
||||
);
|
||||
const isTypeable = isActiveElementTypeable(e);
|
||||
|
||||
if (isEventFromAnInputBox || isEventFromProseMirror) {
|
||||
if (isTypeable) {
|
||||
if (isEscape(e)) {
|
||||
e.target.blur();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class Label < ApplicationRecord
|
||||
uniqueness: { scope: :account_id }
|
||||
|
||||
after_update_commit :update_associated_models
|
||||
default_scope { order(:title) }
|
||||
|
||||
before_validation do
|
||||
self.title = title.downcase if attribute_present?('title')
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"@rails/actioncable": "6.1.3",
|
||||
"@rails/ujs": "^7.0.3-1",
|
||||
"@rails/webpacker": "5.4.4",
|
||||
"@scmmishra/pico-search": "^0.5.1",
|
||||
"@sentry/tracing": "^6.19.7",
|
||||
"@sentry/vue": "^6.19.7",
|
||||
"@sindresorhus/slugify": "1.1.0",
|
||||
@@ -105,6 +106,7 @@
|
||||
"@storybook/addon-links": "6.5.9",
|
||||
"@storybook/addons": "6.5.9",
|
||||
"@storybook/vue": "6.5.9",
|
||||
"@types/jest": "^29.5.2",
|
||||
"@vue/test-utils": "1.1.4",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
|
||||
150
yarn.lock
150
yarn.lock
@@ -2356,6 +2356,13 @@
|
||||
"@types/node" "*"
|
||||
jest-mock "^26.6.2"
|
||||
|
||||
"@jest/expect-utils@^29.5.0":
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-29.5.0.tgz#f74fad6b6e20f924582dc8ecbf2cb800fe43a036"
|
||||
integrity sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==
|
||||
dependencies:
|
||||
jest-get-type "^29.4.3"
|
||||
|
||||
"@jest/fake-timers@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad"
|
||||
@@ -2409,6 +2416,13 @@
|
||||
optionalDependencies:
|
||||
node-notifier "^8.0.0"
|
||||
|
||||
"@jest/schemas@^29.4.3":
|
||||
version "29.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.4.3.tgz#39cf1b8469afc40b6f5a2baaa146e332c4151788"
|
||||
integrity sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==
|
||||
dependencies:
|
||||
"@sinclair/typebox" "^0.25.16"
|
||||
|
||||
"@jest/source-map@^26.6.2":
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535"
|
||||
@@ -2512,6 +2526,18 @@
|
||||
"@types/yargs" "^15.0.0"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@jest/types@^29.5.0":
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.5.0.tgz#f59ef9b031ced83047c67032700d8c807d6e1593"
|
||||
integrity sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==
|
||||
dependencies:
|
||||
"@jest/schemas" "^29.4.3"
|
||||
"@types/istanbul-lib-coverage" "^2.0.0"
|
||||
"@types/istanbul-reports" "^3.0.0"
|
||||
"@types/node" "*"
|
||||
"@types/yargs" "^17.0.8"
|
||||
chalk "^4.0.0"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.0":
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz#cf92a983c83466b8c0ce9124fadeaf09f7c66ea9"
|
||||
@@ -2772,6 +2798,11 @@
|
||||
dependencies:
|
||||
any-observable "^0.3.0"
|
||||
|
||||
"@scmmishra/pico-search@^0.5.1":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@scmmishra/pico-search/-/pico-search-0.5.1.tgz#fe10a5615259623bff0095ad9af26e15d84379cb"
|
||||
integrity sha512-OKyoGy9f0Y47khEpgP3eLw2aKujJe/ndAE0IbovpsVh210GCmECtl8DLYKL+3abcorX3+e6K8S3eFs2HdH8Ksw==
|
||||
|
||||
"@segment/analytics.js-video-plugins@^0.2.1":
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@segment/analytics.js-video-plugins/-/analytics.js-video-plugins-0.2.1.tgz#3596fa3887dcd9df5978dc566edf4a0aea2a9b1e"
|
||||
@@ -2887,6 +2918,11 @@
|
||||
"@sentry/utils" "6.19.7"
|
||||
tslib "^1.9.3"
|
||||
|
||||
"@sinclair/typebox@^0.25.16":
|
||||
version "0.25.24"
|
||||
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718"
|
||||
integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==
|
||||
|
||||
"@sindresorhus/slugify@1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sindresorhus/slugify/-/slugify-1.1.0.tgz#2f195365d9b953384305b62664b44b4036c49430"
|
||||
@@ -4701,6 +4737,14 @@
|
||||
dependencies:
|
||||
"@types/istanbul-lib-report" "*"
|
||||
|
||||
"@types/jest@^29.5.2":
|
||||
version "29.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.2.tgz#86b4afc86e3a8f3005b297ed8a72494f89e6395b"
|
||||
integrity sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==
|
||||
dependencies:
|
||||
expect "^29.0.0"
|
||||
pretty-format "^29.0.0"
|
||||
|
||||
"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
|
||||
version "7.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||
@@ -4904,6 +4948,13 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@types/yargs@^17.0.8":
|
||||
version "17.0.24"
|
||||
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.24.tgz#b3ef8d50ad4aa6aecf6ddc97c580a00f5aa11902"
|
||||
integrity sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@typescript-eslint/experimental-utils@^5.3.0":
|
||||
version "5.30.6"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.30.6.tgz#fe30d2800aedcad7465d9260b66068337df71612"
|
||||
@@ -5583,6 +5634,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
|
||||
dependencies:
|
||||
color-convert "^2.0.1"
|
||||
|
||||
ansi-styles@^5.0.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
ansi-to-html@^0.6.11:
|
||||
version "0.6.14"
|
||||
resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8"
|
||||
@@ -6893,6 +6949,11 @@ ci-info@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
|
||||
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
|
||||
|
||||
ci-info@^3.2.0:
|
||||
version "3.8.0"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
|
||||
integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
|
||||
|
||||
cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
|
||||
@@ -8045,6 +8106,11 @@ diff-sequences@^26.6.2:
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
||||
integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==
|
||||
|
||||
diff-sequences@^29.4.3:
|
||||
version "29.4.3"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2"
|
||||
integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==
|
||||
|
||||
diffie-hellman@^5.0.0:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
|
||||
@@ -8949,6 +9015,17 @@ expect@^26.6.2:
|
||||
jest-message-util "^26.6.2"
|
||||
jest-regex-util "^26.0.0"
|
||||
|
||||
expect@^29.0.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/expect/-/expect-29.5.0.tgz#68c0509156cb2a0adb8865d413b137eeaae682f7"
|
||||
integrity sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==
|
||||
dependencies:
|
||||
"@jest/expect-utils" "^29.5.0"
|
||||
jest-get-type "^29.4.3"
|
||||
jest-matcher-utils "^29.5.0"
|
||||
jest-message-util "^29.5.0"
|
||||
jest-util "^29.5.0"
|
||||
|
||||
express@^4.17.1:
|
||||
version "4.18.2"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
|
||||
@@ -11152,6 +11229,16 @@ jest-diff@^26.6.2:
|
||||
jest-get-type "^26.3.0"
|
||||
pretty-format "^26.6.2"
|
||||
|
||||
jest-diff@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.5.0.tgz#e0d83a58eb5451dcc1fa61b1c3ee4e8f5a290d63"
|
||||
integrity sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
diff-sequences "^29.4.3"
|
||||
jest-get-type "^29.4.3"
|
||||
pretty-format "^29.5.0"
|
||||
|
||||
jest-docblock@^26.0.0:
|
||||
version "26.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5"
|
||||
@@ -11205,6 +11292,11 @@ jest-get-type@^26.3.0:
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0"
|
||||
integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==
|
||||
|
||||
jest-get-type@^29.4.3:
|
||||
version "29.4.3"
|
||||
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-29.4.3.tgz#1ab7a5207c995161100b5187159ca82dd48b3dd5"
|
||||
integrity sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==
|
||||
|
||||
jest-haste-map@^25.5.1:
|
||||
version "25.5.1"
|
||||
resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.5.1.tgz#1df10f716c1d94e60a1ebf7798c9fb3da2620943"
|
||||
@@ -11298,6 +11390,16 @@ jest-matcher-utils@^26.6.2:
|
||||
jest-get-type "^26.3.0"
|
||||
pretty-format "^26.6.2"
|
||||
|
||||
jest-matcher-utils@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz#d957af7f8c0692c5453666705621ad4abc2c59c5"
|
||||
integrity sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==
|
||||
dependencies:
|
||||
chalk "^4.0.0"
|
||||
jest-diff "^29.5.0"
|
||||
jest-get-type "^29.4.3"
|
||||
pretty-format "^29.5.0"
|
||||
|
||||
jest-message-util@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07"
|
||||
@@ -11313,6 +11415,21 @@ jest-message-util@^26.6.2:
|
||||
slash "^3.0.0"
|
||||
stack-utils "^2.0.2"
|
||||
|
||||
jest-message-util@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.5.0.tgz#1f776cac3aca332ab8dd2e3b41625435085c900e"
|
||||
integrity sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.12.13"
|
||||
"@jest/types" "^29.5.0"
|
||||
"@types/stack-utils" "^2.0.0"
|
||||
chalk "^4.0.0"
|
||||
graceful-fs "^4.2.9"
|
||||
micromatch "^4.0.4"
|
||||
pretty-format "^29.5.0"
|
||||
slash "^3.0.0"
|
||||
stack-utils "^2.0.3"
|
||||
|
||||
jest-mock@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302"
|
||||
@@ -11490,6 +11607,18 @@ jest-util@^26.6.2:
|
||||
is-ci "^2.0.0"
|
||||
micromatch "^4.0.2"
|
||||
|
||||
jest-util@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.5.0.tgz#24a4d3d92fc39ce90425311b23c27a6e0ef16b8f"
|
||||
integrity sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==
|
||||
dependencies:
|
||||
"@jest/types" "^29.5.0"
|
||||
"@types/node" "*"
|
||||
chalk "^4.0.0"
|
||||
ci-info "^3.2.0"
|
||||
graceful-fs "^4.2.9"
|
||||
picomatch "^2.2.3"
|
||||
|
||||
jest-validate@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec"
|
||||
@@ -14546,6 +14675,15 @@ pretty-format@^26.6.2:
|
||||
ansi-styles "^4.0.0"
|
||||
react-is "^17.0.1"
|
||||
|
||||
pretty-format@^29.0.0, pretty-format@^29.5.0:
|
||||
version "29.5.0"
|
||||
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.5.0.tgz#283134e74f70e2e3e7229336de0e4fce94ccde5a"
|
||||
integrity sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==
|
||||
dependencies:
|
||||
"@jest/schemas" "^29.4.3"
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
pretty-hrtime@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
|
||||
@@ -15133,6 +15271,11 @@ react-is@^17.0.1:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||
|
||||
react-is@^18.0.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-syntax-highlighter@^15.4.5:
|
||||
version "15.5.0"
|
||||
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20"
|
||||
@@ -16451,6 +16594,13 @@ stack-utils@^2.0.2:
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
stack-utils@^2.0.3:
|
||||
version "2.0.6"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
|
||||
integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==
|
||||
dependencies:
|
||||
escape-string-regexp "^2.0.0"
|
||||
|
||||
state-toggle@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe"
|
||||
|
||||
Reference in New Issue
Block a user