Added improved Admin search behind labs flag (#20363)
ref https://linear.app/tryghost/issue/MOM-117 ref https://linear.app/tryghost/issue/MOM-70 - moved current search into new `search-provider` service and updated `search` service to use the provider service internally - added `search-provider-beta` service - uses `flexsearch` as the underlying index for each document so we have better indexing and matching compared to the naive exact-match search we had previously - adds `excerpt` matching for posts and pages - keeps results output the same as the original search provider - added `internalLinkingSearchImprovements` labs flag so we can test this internally before reaching our internal linking beta testers - updated `search` service to switch between providers based on labs flag
This commit is contained in:
parent
bea074b23d
commit
54812dc67a
@ -63,6 +63,10 @@ const features = [{
|
||||
title: 'Internal Linking @-links (internal alpha)',
|
||||
description: 'Adds internal URL search when typing @ in the editor',
|
||||
flag: 'internalLinkingAtLinks'
|
||||
},{
|
||||
title: 'Internal Linking search improvements (internal alpha)',
|
||||
description: 'Replaces Admin\'s search with flexsearch indexes',
|
||||
flag: 'internalLinkingSearchImprovements'
|
||||
},{
|
||||
title: 'ActivityPub',
|
||||
description: '(Highly) Experimental support for ActivityPub.',
|
||||
|
@ -84,6 +84,7 @@ export default class FeatureService extends Service {
|
||||
@feature('ActivityPub') ActivityPub;
|
||||
@feature('internalLinking') internalLinking;
|
||||
@feature('internalLinkingAtLinks') internalLinkingAtLinks;
|
||||
@feature('internalLinkingSearchImprovements') internalLinkingSearchImprovements;
|
||||
@feature('editorExcerpt') editorExcerpt;
|
||||
@feature('newsletterExcerpt') newsletterExcerpt;
|
||||
|
||||
|
136
ghost/admin/app/services/search-provider-beta.js
Normal file
136
ghost/admin/app/services/search-provider-beta.js
Normal file
@ -0,0 +1,136 @@
|
||||
import RSVP from 'rsvp';
|
||||
import Service from '@ember/service';
|
||||
import {default as Flexsearch} from 'flexsearch';
|
||||
import {isEmpty} from '@ember/utils';
|
||||
import {pluralize} from 'ember-inflector';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
const {Document} = Flexsearch;
|
||||
|
||||
export const SEARCHABLES = [
|
||||
{
|
||||
name: 'Staff',
|
||||
model: 'user',
|
||||
fields: ['id', 'slug', 'url', 'name', 'profile_image'],
|
||||
pathField: 'slug',
|
||||
titleField: 'name',
|
||||
index: ['name']
|
||||
},
|
||||
{
|
||||
name: 'Tags',
|
||||
model: 'tag',
|
||||
fields: ['id', 'slug', 'url', 'name'],
|
||||
pathField: 'slug',
|
||||
titleField: 'name',
|
||||
index: ['name']
|
||||
},
|
||||
{
|
||||
name: 'Posts',
|
||||
model: 'post',
|
||||
fields: ['id', 'url', 'title', 'excerpt', 'status', 'published_at', 'visibility'],
|
||||
pathField: 'id',
|
||||
titleField: 'title',
|
||||
index: ['title', 'excerpt']
|
||||
},
|
||||
{
|
||||
name: 'Pages',
|
||||
model: 'page',
|
||||
fields: ['id', 'url', 'title', 'excerpt', 'status', 'published_at', 'visibility'],
|
||||
pathField: 'id',
|
||||
titleField: 'title',
|
||||
index: ['title', 'excerpt']
|
||||
}
|
||||
];
|
||||
|
||||
export default class SearchProviderService extends Service {
|
||||
@service ajax;
|
||||
@service notifications;
|
||||
@service store;
|
||||
|
||||
indexes = SEARCHABLES.reduce((indexes, searchable) => {
|
||||
indexes[searchable.model] = new Document({
|
||||
tokenize: 'forward',
|
||||
document: {
|
||||
id: 'id',
|
||||
index: searchable.index,
|
||||
store: true
|
||||
}
|
||||
});
|
||||
|
||||
return indexes;
|
||||
}, {});
|
||||
|
||||
/* eslint-disable require-yield */
|
||||
@task
|
||||
*searchTask(term) {
|
||||
const results = [];
|
||||
|
||||
SEARCHABLES.forEach((searchable) => {
|
||||
const searchResults = this.indexes[searchable.model].search(term, {enrich: true});
|
||||
const usedIds = new Set();
|
||||
const groupResults = [];
|
||||
|
||||
searchResults.forEach((field) => {
|
||||
field.result.forEach((searchResult) => {
|
||||
const {id, doc} = searchResult;
|
||||
|
||||
if (usedIds.has(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
usedIds.add(id);
|
||||
|
||||
groupResults.push({
|
||||
id: `${searchable.model}.${doc[searchable.pathField]}`,
|
||||
title: doc[searchable.titleField],
|
||||
groupName: searchable.name,
|
||||
status: doc.status,
|
||||
visibility: doc.visibility,
|
||||
publishedAt: doc.published_at
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (!isEmpty(groupResults)) {
|
||||
results.push({
|
||||
groupName: searchable.name,
|
||||
options: groupResults
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
/* eslint-enable require-yield */
|
||||
|
||||
@task
|
||||
*refreshContentTask() {
|
||||
try {
|
||||
const promises = SEARCHABLES.map(searchable => this.#loadSearchable(searchable));
|
||||
yield RSVP.all(promises);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async #loadSearchable(searchable) {
|
||||
const url = `${this.store.adapterFor(searchable.model).urlForQuery({}, searchable.model)}/`;
|
||||
const query = {fields: searchable.fields, limit: 10000};
|
||||
|
||||
try {
|
||||
const response = await this.ajax.request(url, {data: query});
|
||||
|
||||
response[pluralize(searchable.model)].forEach((item) => {
|
||||
this.indexes[searchable.model].add(item);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error); // eslint-disable-line
|
||||
|
||||
this.notifications.showAPIError(error, {
|
||||
key: `search.load${searchable.name}.error`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
116
ghost/admin/app/services/search-provider.js
Normal file
116
ghost/admin/app/services/search-provider.js
Normal file
@ -0,0 +1,116 @@
|
||||
import RSVP from 'rsvp';
|
||||
import Service from '@ember/service';
|
||||
import {isEmpty} from '@ember/utils';
|
||||
import {pluralize} from 'ember-inflector';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export const SEARCHABLES = [
|
||||
{
|
||||
name: 'Staff',
|
||||
model: 'user',
|
||||
fields: ['id', 'slug', 'url', 'name'], // id not used but required for API to have correct url
|
||||
idField: 'slug',
|
||||
titleField: 'name'
|
||||
},
|
||||
{
|
||||
name: 'Tags',
|
||||
model: 'tag',
|
||||
fields: ['slug', 'url', 'name'],
|
||||
idField: 'slug',
|
||||
titleField: 'name'
|
||||
},
|
||||
{
|
||||
name: 'Posts',
|
||||
model: 'post',
|
||||
fields: ['id', 'url', 'title', 'status', 'published_at', 'visibility'],
|
||||
idField: 'id',
|
||||
titleField: 'title'
|
||||
},
|
||||
{
|
||||
name: 'Pages',
|
||||
model: 'page',
|
||||
fields: ['id', 'url', 'title', 'status', 'published_at', 'visibility'],
|
||||
idField: 'id',
|
||||
titleField: 'title'
|
||||
}
|
||||
];
|
||||
|
||||
export default class SearchProviderService extends Service {
|
||||
@service ajax;
|
||||
@service notifications;
|
||||
@service store;
|
||||
|
||||
content = [];
|
||||
|
||||
/* eslint-disable require-yield */
|
||||
@task
|
||||
*searchTask(term) {
|
||||
const normalizedTerm = term.toString().toLowerCase();
|
||||
const results = [];
|
||||
|
||||
SEARCHABLES.forEach((searchable) => {
|
||||
const matchedContent = this.content.filter((item) => {
|
||||
const normalizedTitle = item.title.toString().toLowerCase();
|
||||
return (
|
||||
item.groupName === searchable.name &&
|
||||
normalizedTitle.indexOf(normalizedTerm) >= 0
|
||||
);
|
||||
});
|
||||
|
||||
if (!isEmpty(matchedContent)) {
|
||||
results.push({
|
||||
groupName: searchable.name,
|
||||
options: matchedContent
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
/* eslint-enable require-yield */
|
||||
|
||||
@task
|
||||
*refreshContentTask() {
|
||||
const content = [];
|
||||
const promises = SEARCHABLES.map(searchable => this._loadSearchable(searchable, content));
|
||||
|
||||
try {
|
||||
yield RSVP.all(promises);
|
||||
this.content = content;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async _loadSearchable(searchable, content) {
|
||||
const url = `${this.store.adapterFor(searchable.model).urlForQuery({}, searchable.model)}/`;
|
||||
const maxSearchableLimit = '10000';
|
||||
const query = {fields: searchable.fields, limit: maxSearchableLimit};
|
||||
|
||||
try {
|
||||
const response = await this.ajax.request(url, {data: query});
|
||||
|
||||
const items = response[pluralize(searchable.model)].map(
|
||||
item => ({
|
||||
id: `${searchable.model}.${item[searchable.idField]}`,
|
||||
url: item.url,
|
||||
title: item[searchable.titleField],
|
||||
groupName: searchable.name,
|
||||
status: item.status,
|
||||
visibility: item.visibility,
|
||||
publishedAt: item.published_at
|
||||
})
|
||||
);
|
||||
|
||||
content.push(...items);
|
||||
} catch (error) {
|
||||
console.error(error); // eslint-disable-line
|
||||
|
||||
this.notifications.showAPIError(error, {
|
||||
key: `search.load${searchable.name}.error`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,49 +1,24 @@
|
||||
import RSVP from 'rsvp';
|
||||
import Service from '@ember/service';
|
||||
import {action} from '@ember/object';
|
||||
import {isBlank, isEmpty} from '@ember/utils';
|
||||
import {pluralize} from 'ember-inflector';
|
||||
import {isBlank} from '@ember/utils';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
export default class SearchService extends Service {
|
||||
@service ajax;
|
||||
@service feature;
|
||||
@service notifications;
|
||||
@service searchProvider;
|
||||
@service searchProviderBeta;
|
||||
@service store;
|
||||
|
||||
content = [];
|
||||
isContentStale = true;
|
||||
|
||||
searchables = [
|
||||
{
|
||||
name: 'Staff',
|
||||
model: 'user',
|
||||
fields: ['id', 'slug', 'url', 'name'], // id not used but required for API to have correct url
|
||||
idField: 'slug',
|
||||
titleField: 'name'
|
||||
},
|
||||
{
|
||||
name: 'Tags',
|
||||
model: 'tag',
|
||||
fields: ['slug', 'url', 'name'],
|
||||
idField: 'slug',
|
||||
titleField: 'name'
|
||||
},
|
||||
{
|
||||
name: 'Posts',
|
||||
model: 'post',
|
||||
fields: ['id', 'url', 'title', 'status', 'published_at', 'visibility'],
|
||||
idField: 'id',
|
||||
titleField: 'title'
|
||||
},
|
||||
{
|
||||
name: 'Pages',
|
||||
model: 'page',
|
||||
fields: ['id', 'url', 'title', 'status', 'published_at', 'visibility'],
|
||||
idField: 'id',
|
||||
titleField: 'title'
|
||||
}
|
||||
];
|
||||
get provider() {
|
||||
return this.feature.internalLinkingSearchImprovements
|
||||
? this.searchProviderBeta
|
||||
: this.searchProvider;
|
||||
}
|
||||
|
||||
@action
|
||||
expireContent() {
|
||||
@ -67,33 +42,7 @@ export default class SearchService extends Service {
|
||||
yield this.refreshContentTask.lastRunning;
|
||||
}
|
||||
|
||||
const searchResult = this._searchContent(term);
|
||||
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
_searchContent(term) {
|
||||
const normalizedTerm = term.toString().toLowerCase();
|
||||
const results = [];
|
||||
|
||||
this.searchables.forEach((searchable) => {
|
||||
const matchedContent = this.content.filter((item) => {
|
||||
const normalizedTitle = item.title.toString().toLowerCase();
|
||||
return (
|
||||
item.groupName === searchable.name &&
|
||||
normalizedTitle.indexOf(normalizedTerm) >= 0
|
||||
);
|
||||
});
|
||||
|
||||
if (!isEmpty(matchedContent)) {
|
||||
results.push({
|
||||
groupName: searchable.name,
|
||||
options: matchedContent
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
return yield this.provider.searchTask.perform(term);
|
||||
}
|
||||
|
||||
@task({drop: true})
|
||||
@ -104,47 +53,8 @@ export default class SearchService extends Service {
|
||||
|
||||
this.isContentStale = true;
|
||||
|
||||
const content = [];
|
||||
const promises = this.searchables.map(searchable => this._loadSearchable(searchable, content));
|
||||
|
||||
try {
|
||||
yield RSVP.all(promises);
|
||||
this.content = content;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line
|
||||
console.error(error);
|
||||
}
|
||||
yield this.provider.refreshContentTask.perform();
|
||||
|
||||
this.isContentStale = false;
|
||||
}
|
||||
|
||||
async _loadSearchable(searchable, content) {
|
||||
const url = `${this.store.adapterFor(searchable.model).urlForQuery({}, searchable.model)}/`;
|
||||
const maxSearchableLimit = '10000';
|
||||
const query = {fields: searchable.fields, limit: maxSearchableLimit};
|
||||
|
||||
try {
|
||||
const response = await this.ajax.request(url, {data: query});
|
||||
|
||||
const items = response[pluralize(searchable.model)].map(
|
||||
item => ({
|
||||
id: `${searchable.model}.${item[searchable.idField]}`,
|
||||
url: item.url,
|
||||
title: item[searchable.titleField],
|
||||
groupName: searchable.name,
|
||||
status: item.status,
|
||||
visibility: item.visibility,
|
||||
publishedAt: item.published_at
|
||||
})
|
||||
);
|
||||
|
||||
content.push(...items);
|
||||
} catch (error) {
|
||||
console.error(error); // eslint-disable-line
|
||||
|
||||
this.notifications.showAPIError(error, {
|
||||
key: `search.load${searchable.name}.error`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,6 +123,7 @@
|
||||
"ember-websockets": "10.2.1",
|
||||
"eslint-plugin-babel": "5.3.1",
|
||||
"faker": "5.5.3",
|
||||
"flexsearch": "0.7.43",
|
||||
"fs-extra": "11.2.0",
|
||||
"glob": "8.1.0",
|
||||
"google-caja-bower": "https://github.com/acburdine/google-caja-bower#ghost",
|
||||
@ -205,4 +206,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,167 +2,184 @@ import ctrlOrCmd from 'ghost-admin/utils/ctrl-or-cmd';
|
||||
import {authenticateSession} from 'ember-simple-auth/test-support';
|
||||
import {click, currentURL, find, findAll, triggerKeyEvent, visit} from '@ember/test-helpers';
|
||||
import {describe, it} from 'mocha';
|
||||
import {enableLabsFlag} from '../helpers/labs-flag';
|
||||
import {expect} from 'chai';
|
||||
import {getPosts} from '../../mirage/config/posts';
|
||||
import {setupApplicationTest} from 'ember-mocha';
|
||||
import {setupMirage} from 'ember-cli-mirage/test-support';
|
||||
import {typeInSearch} from 'ember-power-select/test-support/helpers';
|
||||
|
||||
describe('Acceptance: Search', function () {
|
||||
const trigger = '[data-test-modal="search"] .ember-power-select-trigger';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let firstUser, firstPost, secondPost, firstPage, firstTag;
|
||||
const suites = [{
|
||||
name: 'Acceptance: Search',
|
||||
beforeEach() {
|
||||
// noop
|
||||
}
|
||||
}, {
|
||||
name: 'Acceptance: Search (beta)',
|
||||
beforeEach() {
|
||||
enableLabsFlag(this.server, 'internalLinking');
|
||||
}
|
||||
}];
|
||||
|
||||
const hooks = setupApplicationTest();
|
||||
setupMirage(hooks);
|
||||
suites.forEach((suite) => {
|
||||
describe(suite.name, function () {
|
||||
const trigger = '[data-test-modal="search"] .ember-power-select-trigger';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let firstUser, firstPost, secondPost, firstPage, firstTag;
|
||||
|
||||
this.beforeEach(async function () {
|
||||
this.server.loadFixtures();
|
||||
const hooks = setupApplicationTest();
|
||||
setupMirage(hooks);
|
||||
|
||||
// create user to authenticate as
|
||||
let role = this.server.create('role', {name: 'Owner'});
|
||||
firstUser = this.server.create('user', {roles: [role], slug: 'owner', name: 'First user'});
|
||||
this.beforeEach(async function () {
|
||||
this.server.loadFixtures();
|
||||
|
||||
// populate store with data we'll be searching
|
||||
firstPost = this.server.create('post', {title: 'First post', slug: 'first-post'});
|
||||
secondPost = this.server.create('post', {title: 'Second post', slug: 'second-post'});
|
||||
firstPage = this.server.create('page', {title: 'First page', slug: 'first-page'});
|
||||
firstTag = this.server.create('tag', {name: 'First tag', slug: 'first-tag'});
|
||||
// create user to authenticate as
|
||||
let role = this.server.create('role', {name: 'Owner'});
|
||||
firstUser = this.server.create('user', {roles: [role], slug: 'owner', name: 'First user'});
|
||||
|
||||
return await authenticateSession();
|
||||
});
|
||||
// populate store with data we'll be searching
|
||||
firstPost = this.server.create('post', {title: 'First post', slug: 'first-post'});
|
||||
secondPost = this.server.create('post', {title: 'Second post', slug: 'second-post'});
|
||||
firstPage = this.server.create('page', {title: 'First page', slug: 'first-page'});
|
||||
firstTag = this.server.create('tag', {name: 'First tag', slug: 'first-tag'});
|
||||
|
||||
it('opens search modal when clicking icon', async function () {
|
||||
await visit('/dashboard');
|
||||
expect(currentURL(), 'currentURL').to.equal('/dashboard');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
await click('[data-test-button="search"]');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
});
|
||||
suite.beforeEach.bind(this)();
|
||||
|
||||
it('opens search icon when pressing Ctrl/Cmd+K', async function () {
|
||||
await visit('/dashboard');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
await triggerKeyEvent(document, 'keydown', 'K', {
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
return await authenticateSession();
|
||||
});
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
});
|
||||
|
||||
it('closes search modal on escape key', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
await triggerKeyEvent(document, 'keydown', 'Escape');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
});
|
||||
it('opens search modal when clicking icon', async function () {
|
||||
await visit('/dashboard');
|
||||
expect(currentURL(), 'currentURL').to.equal('/dashboard');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
await click('[data-test-button="search"]');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
});
|
||||
|
||||
it('closes search modal on click outside', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
await click('.epm-backdrop');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
});
|
||||
it('opens search icon when pressing Ctrl/Cmd+K', async function () {
|
||||
await visit('/dashboard');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
await triggerKeyEvent(document, 'keydown', 'K', {
|
||||
metaKey: ctrlOrCmd === 'command',
|
||||
ctrlKey: ctrlOrCmd === 'ctrl'
|
||||
});
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
});
|
||||
|
||||
it('finds posts, pages, staff, and tags when typing', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first'); // search is not case sensitive
|
||||
it('closes search modal on escape key', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
await triggerKeyEvent(document, 'keydown', 'Escape');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
});
|
||||
|
||||
// all groups are present
|
||||
const groupNames = findAll('.ember-power-select-group-name');
|
||||
expect(groupNames, 'group names').to.have.length(4);
|
||||
expect(groupNames.map(el => el.textContent.trim())).to.deep.equal(['Staff', 'Tags', 'Posts', 'Pages']);
|
||||
it('closes search modal on click outside', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.exist;
|
||||
await click('.epm-backdrop');
|
||||
expect(find('[data-test-modal="search"]'), 'search modal').to.not.exist;
|
||||
});
|
||||
|
||||
// correct results are found
|
||||
const options = findAll('.ember-power-select-option');
|
||||
expect(options, 'number of search results').to.have.length(4);
|
||||
expect(options.map(el => el.textContent.trim())).to.deep.equal(['First user', 'First tag', 'First post', 'First page']);
|
||||
it('finds posts, pages, staff, and tags when typing', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first'); // search is not case sensitive
|
||||
|
||||
// first item is selected
|
||||
expect(options[0]).to.have.attribute('aria-current', 'true');
|
||||
});
|
||||
// all groups are present
|
||||
const groupNames = findAll('.ember-power-select-group-name');
|
||||
expect(groupNames, 'group names').to.have.length(4);
|
||||
expect(groupNames.map(el => el.textContent.trim())).to.deep.equal(['Staff', 'Tags', 'Posts', 'Pages']);
|
||||
|
||||
it('up/down arrows move selected item', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first post');
|
||||
expect(findAll('.ember-power-select-option')[0], 'first option (initial)').to.have.attribute('aria-current', 'true');
|
||||
await triggerKeyEvent(trigger, 'keyup', 'ArrowDown');
|
||||
expect(findAll('.ember-power-select-option')[0], 'second option (after down)').to.have.attribute('aria-current', 'true');
|
||||
await triggerKeyEvent(trigger, 'keyup', 'ArrowUp');
|
||||
expect(findAll('.ember-power-select-option')[0], 'first option (after up)').to.have.attribute('aria-current', 'true');
|
||||
});
|
||||
// correct results are found
|
||||
const options = findAll('.ember-power-select-option');
|
||||
expect(options, 'number of search results').to.have.length(4);
|
||||
expect(options.map(el => el.textContent.trim())).to.deep.equal(['First user', 'First tag', 'First post', 'First page']);
|
||||
|
||||
it('navigates to editor when post selected (Enter)', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first post');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting post').to.equal(`/editor/post/${firstPost.id}`);
|
||||
});
|
||||
// first item is selected
|
||||
expect(options[0]).to.have.attribute('aria-current', 'true');
|
||||
});
|
||||
|
||||
it('navigates to editor when post selected (Clicked)', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first post');
|
||||
await click('.ember-power-select-option[aria-current="true"]');
|
||||
expect(currentURL(), 'url after selecting post').to.equal(`/editor/post/${firstPost.id}`);
|
||||
});
|
||||
it('up/down arrows move selected item', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first post');
|
||||
expect(findAll('.ember-power-select-option')[0], 'first option (initial)').to.have.attribute('aria-current', 'true');
|
||||
await triggerKeyEvent(trigger, 'keyup', 'ArrowDown');
|
||||
expect(findAll('.ember-power-select-option')[0], 'second option (after down)').to.have.attribute('aria-current', 'true');
|
||||
await triggerKeyEvent(trigger, 'keyup', 'ArrowUp');
|
||||
expect(findAll('.ember-power-select-option')[0], 'first option (after up)').to.have.attribute('aria-current', 'true');
|
||||
});
|
||||
|
||||
it('navigates to editor when page selected', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('page');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting page').to.equal(`/editor/page/${firstPage.id}`);
|
||||
});
|
||||
it('navigates to editor when post selected (Enter)', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first post');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting post').to.equal(`/editor/post/${firstPost.id}`);
|
||||
});
|
||||
|
||||
it('navigates to tag edit screen when tag selected', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('tag');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting tag').to.equal(`/tags/${firstTag.slug}`);
|
||||
});
|
||||
it('navigates to editor when post selected (Clicked)', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first post');
|
||||
await click('.ember-power-select-option[aria-current="true"]');
|
||||
expect(currentURL(), 'url after selecting post').to.equal(`/editor/post/${firstPost.id}`);
|
||||
});
|
||||
|
||||
// TODO: Staff settings are now part of AdminX so this isn't working, can we test AdminX from Ember tests?
|
||||
it.skip('navigates to user edit screen when user selected', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('user');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting user').to.equal(`/settings/staff/${firstUser.slug}`);
|
||||
});
|
||||
it('navigates to editor when page selected', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('page');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting page').to.equal(`/editor/page/${firstPage.id}`);
|
||||
});
|
||||
|
||||
it('shows no results message when no results', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('x');
|
||||
expect(find('.ember-power-select-option--no-matches-message'), 'no results message').to.contain.text('No results found');
|
||||
});
|
||||
it('navigates to tag edit screen when tag selected', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('tag');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting tag').to.equal(`/tags/${firstTag.slug}`);
|
||||
});
|
||||
|
||||
// https://linear.app/tryghost/issue/MOM-103/search-stalls-on-query-when-refresh-occurs
|
||||
it('handles refresh on first search being slow', async function () {
|
||||
this.server.get('/posts/', getPosts, {timing: 200});
|
||||
// TODO: Staff settings are now part of AdminX so this isn't working, can we test AdminX from Ember tests?
|
||||
it.skip('navigates to user edit screen when user selected', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('user');
|
||||
await triggerKeyEvent(trigger, 'keydown', 'Enter');
|
||||
expect(currentURL(), 'url after selecting user').to.equal(`/settings/staff/${firstUser.slug}`);
|
||||
});
|
||||
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first'); // search is not case sensitive
|
||||
it('shows no results message when no results', async function () {
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('x');
|
||||
expect(find('.ember-power-select-option--no-matches-message'), 'no results message').to.contain.text('No results found');
|
||||
});
|
||||
|
||||
// all groups are present
|
||||
const groupNames = findAll('.ember-power-select-group-name');
|
||||
expect(groupNames, 'group names').to.have.length(4);
|
||||
expect(groupNames.map(el => el.textContent.trim())).to.deep.equal(['Staff', 'Tags', 'Posts', 'Pages']);
|
||||
// https://linear.app/tryghost/issue/MOM-103/search-stalls-on-query-when-refresh-occurs
|
||||
it('handles refresh on first search being slow', async function () {
|
||||
this.server.get('/posts/', getPosts, {timing: 200});
|
||||
|
||||
// correct results are found
|
||||
const options = findAll('.ember-power-select-option');
|
||||
expect(options, 'number of search results').to.have.length(4);
|
||||
expect(options.map(el => el.textContent.trim())).to.deep.equal(['First user', 'First tag', 'First post', 'First page']);
|
||||
await visit('/dashboard');
|
||||
await click('[data-test-button="search"]');
|
||||
await typeInSearch('first'); // search is not case sensitive
|
||||
|
||||
// first item is selected
|
||||
expect(options[0]).to.have.attribute('aria-current', 'true');
|
||||
// all groups are present
|
||||
const groupNames = findAll('.ember-power-select-group-name');
|
||||
expect(groupNames, 'group names').to.have.length(4);
|
||||
expect(groupNames.map(el => el.textContent.trim())).to.deep.equal(['Staff', 'Tags', 'Posts', 'Pages']);
|
||||
|
||||
// correct results are found
|
||||
const options = findAll('.ember-power-select-option');
|
||||
expect(options, 'number of search results').to.have.length(4);
|
||||
expect(options.map(el => el.textContent.trim())).to.deep.equal(['First user', 'First tag', 'First post', 'First page']);
|
||||
|
||||
// first item is selected
|
||||
expect(options[0]).to.have.attribute('aria-current', 'true');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -54,7 +54,8 @@ const ALPHA_FEATURES = [
|
||||
'importMemberTier',
|
||||
'lexicalIndicators',
|
||||
'adminXDemo',
|
||||
'internalLinkingAtLinks'
|
||||
'internalLinkingAtLinks',
|
||||
'internalLinkingSearchImprovements'
|
||||
];
|
||||
|
||||
module.exports.GA_KEYS = [...GA_FEATURES];
|
||||
|
242
yarn.lock
242
yarn.lock
@ -2430,7 +2430,7 @@
|
||||
ember-cli-babel "^7.22.1"
|
||||
ember-compatibility-helpers "^1.1.1"
|
||||
|
||||
"@ember/render-modifiers@2.1.0", "@ember/render-modifiers@^1.0.2 || ^2.0.0", "@ember/render-modifiers@^2.0.0", "@ember/render-modifiers@^2.0.4":
|
||||
"@ember/render-modifiers@2.1.0", "@ember/render-modifiers@^1.0.2 || ^2.0.0", "@ember/render-modifiers@^2.0.4":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@ember/render-modifiers/-/render-modifiers-2.1.0.tgz#f4fff95a8b5cfbe947ec46644732d511711c5bf9"
|
||||
integrity sha512-LruhfoDv2itpk0fA0IC76Sxjcnq/7BC6txpQo40hOko8Dn6OxwQfxkPIbZGV0Cz7df+iX+VJrcYzNIvlc3w2EQ==
|
||||
@ -2568,19 +2568,6 @@
|
||||
resolve "^1.8.1"
|
||||
semver "^7.3.2"
|
||||
|
||||
"@embroider/macros@0.47.2", "@embroider/macros@^0.47.2":
|
||||
version "0.47.2"
|
||||
resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-0.47.2.tgz#23cbe92cac3c24747f054e1eea2a22538bf7ebd0"
|
||||
integrity sha512-ViNWluJCeM5OPlM3rs8kdOz3RV5rpfXX5D2rDnc/q86xRS0xf4NFEjYRV7W6fBcD0b3v5jSHDTwrjq9Kee4rHg==
|
||||
dependencies:
|
||||
"@embroider/shared-internals" "0.47.2"
|
||||
assert-never "^1.2.1"
|
||||
ember-cli-babel "^7.26.6"
|
||||
find-up "^5.0.0"
|
||||
lodash "^4.17.21"
|
||||
resolve "^1.20.0"
|
||||
semver "^7.3.2"
|
||||
|
||||
"@embroider/macros@1.13.4", "@embroider/macros@^0.50.0 || ^1.0.0", "@embroider/macros@^1.0.0", "@embroider/macros@^1.10.0", "@embroider/macros@^1.12.2", "@embroider/macros@^1.2.0", "@embroider/macros@^1.8.0", "@embroider/macros@^1.8.3", "@embroider/macros@^1.9.0":
|
||||
version "1.13.4"
|
||||
resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-1.13.4.tgz#4fefb79d68bcfbc4619551572b2ca3040a224fb5"
|
||||
@ -2608,19 +2595,6 @@
|
||||
semver "^7.3.2"
|
||||
typescript-memoize "^1.0.0-alpha.3"
|
||||
|
||||
"@embroider/shared-internals@0.47.2":
|
||||
version "0.47.2"
|
||||
resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-0.47.2.tgz#24e9fa0dd9c529d5c996ee1325729ea08d1fa19f"
|
||||
integrity sha512-SxdZYjAE0fiM5zGDz+12euWIsQZ1tsfR1k+NKmiWMyLhA5T3pNgbR2/Djvx/cVIxOtEavGGSllYbzRKBtV4xMg==
|
||||
dependencies:
|
||||
babel-import-util "^0.2.0"
|
||||
ember-rfc176-data "^0.3.17"
|
||||
fs-extra "^9.1.0"
|
||||
lodash "^4.17.21"
|
||||
resolve-package-path "^4.0.1"
|
||||
semver "^7.3.5"
|
||||
typescript-memoize "^1.0.1"
|
||||
|
||||
"@embroider/shared-internals@2.5.1", "@embroider/shared-internals@^2.0.0":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.5.1.tgz#a4d8c057cbff293ef6eb29ee6537f263d206b444"
|
||||
@ -2681,15 +2655,6 @@
|
||||
broccoli-funnel "^3.0.5"
|
||||
ember-cli-babel "^7.23.1"
|
||||
|
||||
"@embroider/util@^0.47.2":
|
||||
version "0.47.2"
|
||||
resolved "https://registry.yarnpkg.com/@embroider/util/-/util-0.47.2.tgz#d06497b4b84c07ed9c7b628293bb019c533f4556"
|
||||
integrity sha512-g9OqnFJPktGu9NS0Ug3Pxz1JE3jeDceeVE4IrlxDrVmBXMA/GrBvpwjolWgl6jh97cMJyExdz62jIvPHV4256Q==
|
||||
dependencies:
|
||||
"@embroider/macros" "0.47.2"
|
||||
broccoli-funnel "^3.0.5"
|
||||
ember-cli-babel "^7.23.1"
|
||||
|
||||
"@emotion/babel-plugin@^11.11.0":
|
||||
version "11.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
|
||||
@ -3347,6 +3312,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||
|
||||
"@isaacs/cliui@^8.0.2":
|
||||
version "8.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550"
|
||||
integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
|
||||
dependencies:
|
||||
string-width "^5.1.2"
|
||||
string-width-cjs "npm:string-width@^4.2.0"
|
||||
strip-ansi "^7.0.1"
|
||||
strip-ansi-cjs "npm:strip-ansi@^6.0.1"
|
||||
wrap-ansi "^8.1.0"
|
||||
wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
|
||||
|
||||
"@isaacs/ttlcache@1.4.1":
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2"
|
||||
@ -6952,7 +6929,7 @@
|
||||
"@tryghost/root-utils" "^0.3.28"
|
||||
debug "^4.3.1"
|
||||
|
||||
"@tryghost/elasticsearch@^3.0.19":
|
||||
"@tryghost/elasticsearch@^3.0.16", "@tryghost/elasticsearch@^3.0.19":
|
||||
version "3.0.19"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/elasticsearch/-/elasticsearch-3.0.19.tgz#a0a94b667c83575a57775027aea5cb4ff4f216ef"
|
||||
integrity sha512-7yOPEnkebsMuHeIH5oYRQoHa1vz1AkjHRPN4GWJhcywrH20S3Kj66oUPj+8jyqDOvfwqPc7Vfa6jc7rTcy3AQQ==
|
||||
@ -6982,7 +6959,15 @@
|
||||
focus-trap "^6.7.2"
|
||||
postcss-preset-env "^7.3.1"
|
||||
|
||||
"@tryghost/errors@1.3.1", "@tryghost/errors@1.3.2", "@tryghost/errors@^1.2.26", "@tryghost/errors@^1.2.3", "@tryghost/errors@^1.3.2":
|
||||
"@tryghost/errors@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.3.1.tgz#32a00c5e5293c46e54d03a66da871ac34b2ab35c"
|
||||
integrity sha512-iZqT0vZ3NVZNq9o1HYxW00k1mcUAC+t5OLiI8O29/uQwAfy7NemY+Cabl9mWoIwgvBmw7l0Z8pHTcXMo1c+xMw==
|
||||
dependencies:
|
||||
"@stdlib/utils-copy" "^0.0.7"
|
||||
uuid "^9.0.0"
|
||||
|
||||
"@tryghost/errors@1.3.2", "@tryghost/errors@^1.2.26", "@tryghost/errors@^1.2.3", "@tryghost/errors@^1.3.2":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/errors/-/errors-1.3.2.tgz#3612f6f59ca07e37d1095f9eb31ff6a069a3b7f2"
|
||||
integrity sha512-Aqi2vz7HHwN6p2juIYUu8vpMtaKavjULBKNnL0l1req9qXjPs90i/HV8zhvK0ceeWuPdEXaCkfHSRr/yxG3/uw==
|
||||
@ -7020,7 +7005,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/http-cache-utils/-/http-cache-utils-0.1.15.tgz#721ffd1f3b7173da679f6c8c25cda6f0c728264b"
|
||||
integrity sha512-G0x9ZUkBbTWIFg2Dnng6qKLststZKuUYx98pcowNwpYuEGUPOka4eAJj3frz/60F1CzMb1qYy9WEjD/xRGd5oQ==
|
||||
|
||||
"@tryghost/http-stream@^0.1.30":
|
||||
"@tryghost/http-stream@^0.1.27", "@tryghost/http-stream@^0.1.30":
|
||||
version "0.1.30"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/http-stream/-/http-stream-0.1.30.tgz#ef2a9a55a2ec7fab7e889a11ce23e7ebfaab8a0b"
|
||||
integrity sha512-ZlcwtWQICN2OeHcVAL+BXxkzO5tICdLYZeQ2EuVk2E+mC+1ARFrpVUbpTnIbCXDL+uV3cOLqPh2KNGGJAdDChw==
|
||||
@ -7206,7 +7191,24 @@
|
||||
lodash "^4.17.21"
|
||||
luxon "^1.26.0"
|
||||
|
||||
"@tryghost/logging@2.4.10", "@tryghost/logging@2.4.15", "@tryghost/logging@^2.4.7":
|
||||
"@tryghost/logging@2.4.10":
|
||||
version "2.4.10"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/logging/-/logging-2.4.10.tgz#2e5b56c53364be330c1e6f2ffa33e3c30b7bac8e"
|
||||
integrity sha512-l356vLSQmszY14y7ef5YxY4CZ3418NXn5+LvFdlweeTRk0ilWx1mVUoXi8IlVh90rIVbemv+pXi1dusJB6peQA==
|
||||
dependencies:
|
||||
"@tryghost/bunyan-rotating-filestream" "^0.0.7"
|
||||
"@tryghost/elasticsearch" "^3.0.16"
|
||||
"@tryghost/http-stream" "^0.1.27"
|
||||
"@tryghost/pretty-stream" "^0.1.21"
|
||||
"@tryghost/root-utils" "^0.3.25"
|
||||
bunyan "^1.8.15"
|
||||
bunyan-loggly "^1.4.2"
|
||||
fs-extra "^11.0.0"
|
||||
gelf-stream "^1.1.1"
|
||||
json-stringify-safe "^5.0.1"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@tryghost/logging@2.4.15", "@tryghost/logging@^2.4.7":
|
||||
version "2.4.15"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/logging/-/logging-2.4.15.tgz#a94e37d760a62d6f2fc2868e4cd8bf6f219b2a2e"
|
||||
integrity sha512-mSVdSR/9bd1D/DCFpfeFn2AnPE/0lK78ePHBrtteOipA7ogL0Kd+QvabHK5iKLe+/20flBZs4BvnU/DBuS8Pvw==
|
||||
@ -7295,7 +7297,7 @@
|
||||
chalk "^4.1.0"
|
||||
sywac "^1.3.0"
|
||||
|
||||
"@tryghost/pretty-stream@^0.1.24":
|
||||
"@tryghost/pretty-stream@^0.1.21", "@tryghost/pretty-stream@^0.1.24":
|
||||
version "0.1.24"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/pretty-stream/-/pretty-stream-0.1.24.tgz#f670dd66d0d3f7fa733f72294b42fc1b40b0fbd3"
|
||||
integrity sha512-s83KJXIt2nN4JRtBkkWWPLgmh8d/FKd8pvlln4L+EOKkNiVie2vEeATphFsWiK2NZOc4GSgv/fZHxoKBvnOrnA==
|
||||
@ -7326,7 +7328,7 @@
|
||||
got "13.0.0"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"@tryghost/root-utils@0.3.28", "@tryghost/root-utils@^0.3.24", "@tryghost/root-utils@^0.3.28":
|
||||
"@tryghost/root-utils@0.3.28", "@tryghost/root-utils@^0.3.24", "@tryghost/root-utils@^0.3.25", "@tryghost/root-utils@^0.3.28":
|
||||
version "0.3.28"
|
||||
resolved "https://registry.yarnpkg.com/@tryghost/root-utils/-/root-utils-0.3.28.tgz#43ae0047927a7753c9b526ea12ce6e382ec7fb1f"
|
||||
integrity sha512-/izwMw9tCJIQ3DVHumzEWgKhKAw5FwTgrrYcCNHl89yijJKaVRBOJUhlB/u2ST6UWfhahodjaYauq7ymTItaeg==
|
||||
@ -8940,7 +8942,7 @@ ansi-styles@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
|
||||
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
|
||||
|
||||
ansi-styles@^6.0.0, ansi-styles@^6.2.1:
|
||||
ansi-styles@^6.0.0, ansi-styles@^6.1.0, ansi-styles@^6.2.1:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5"
|
||||
integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
|
||||
@ -13976,6 +13978,11 @@ duplexify@^3.4.2, duplexify@^3.5.0, duplexify@^3.6.0:
|
||||
readable-stream "^2.0.0"
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
eastasianwidth@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
|
||||
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
|
||||
|
||||
ecc-jsbn@~0.1.1:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||
@ -14161,7 +14168,7 @@ ember-auto-import@2.7.3, "ember-auto-import@^1.12.1 || ^2.4.3", ember-auto-impor
|
||||
typescript-memoize "^1.0.0-alpha.3"
|
||||
walk-sync "^3.0.0"
|
||||
|
||||
ember-auto-import@^1.11.2, ember-auto-import@^1.11.3, ember-auto-import@^1.12.0:
|
||||
ember-auto-import@^1.11.3, ember-auto-import@^1.12.0:
|
||||
version "1.12.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-auto-import/-/ember-auto-import-1.12.2.tgz#cc7298ee5c0654b0249267de68fb27a2861c3579"
|
||||
integrity sha512-gLqML2k77AuUiXxWNon1FSzuG1DV7PEPpCLCU5aJvf6fdL6rmFfElsZRh+8ELEB/qP9dT+LHjNEunVzd2dYc8A==
|
||||
@ -14196,25 +14203,7 @@ ember-auto-import@^1.11.2, ember-auto-import@^1.11.3, ember-auto-import@^1.12.0:
|
||||
walk-sync "^0.3.3"
|
||||
webpack "^4.43.0"
|
||||
|
||||
ember-basic-dropdown@^3.0.11:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-basic-dropdown/-/ember-basic-dropdown-3.1.0.tgz#47c292de890d1958057736c00b8eb2b8017d530b"
|
||||
integrity sha512-UISvgJHfiJ8FeXqH8ZN+NmoImN8p6Sb+85qlEv853hLuEfEYnFUqLNhea8nNl9CpFqcD3yU4dKbhYtc6nB39aQ==
|
||||
dependencies:
|
||||
"@ember/render-modifiers" "^2.0.0"
|
||||
"@embroider/macros" "^0.47.2"
|
||||
"@embroider/util" "^0.47.2"
|
||||
"@glimmer/component" "^1.0.4"
|
||||
"@glimmer/tracking" "^1.0.4"
|
||||
ember-cli-babel "^7.26.6"
|
||||
ember-cli-htmlbars "^6.0.0"
|
||||
ember-cli-typescript "^4.2.1"
|
||||
ember-element-helper "^0.5.5"
|
||||
ember-maybe-in-element "^2.0.3"
|
||||
ember-style-modifier "^0.7.0"
|
||||
ember-truth-helpers "^2.1.0 || ^3.0.0"
|
||||
|
||||
ember-basic-dropdown@^6.0.0:
|
||||
ember-basic-dropdown@6.0.2, ember-basic-dropdown@^3.0.11, ember-basic-dropdown@^6.0.0:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-basic-dropdown/-/ember-basic-dropdown-6.0.2.tgz#af47dbd544c605cf9cbc62225185616356aeef52"
|
||||
integrity sha512-JgI/cy7eS/Y2WoQl7B2Mko/1aFTAlxr5d+KpQeH7rBKOFml7IQtLvhiDQrpU/FLkrQ9aLNEJtzwtDZV1xQxAKA==
|
||||
@ -14844,7 +14833,7 @@ ember-cli@3.24.0:
|
||||
workerpool "^6.0.3"
|
||||
yam "^1.0.0"
|
||||
|
||||
ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.0, ember-compatibility-helpers@^1.2.1, ember-compatibility-helpers@^1.2.4, ember-compatibility-helpers@^1.2.5:
|
||||
ember-compatibility-helpers@^1.1.1, ember-compatibility-helpers@^1.1.2, ember-compatibility-helpers@^1.2.0, ember-compatibility-helpers@^1.2.1, ember-compatibility-helpers@^1.2.5:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/ember-compatibility-helpers/-/ember-compatibility-helpers-1.2.7.tgz#b4f138bba844f8f38f0b8f4d7e928841cd5e6591"
|
||||
integrity sha512-BtkjulweiXo9c3yVWrtexw2dTmBrvavD/xixNC6TKOBdrixUwU+6nuOO9dufDWsMxoid7MvtmDpzc9+mE8PdaA==
|
||||
@ -14969,7 +14958,7 @@ ember-drag-drop@0.4.8:
|
||||
dependencies:
|
||||
ember-cli-babel "^6.6.0"
|
||||
|
||||
ember-element-helper@^0.5.0, ember-element-helper@^0.5.5:
|
||||
ember-element-helper@^0.5.0:
|
||||
version "0.5.5"
|
||||
resolved "https://registry.yarnpkg.com/ember-element-helper/-/ember-element-helper-0.5.5.tgz#4a9ecb4dce57ee7f5ceb868a53c7b498c729f056"
|
||||
integrity sha512-Tu3hsI+/mjHBUvw62Qi+YDZtKkn59V66CjwbgfNTZZ7aHf4gFm1ow4zJ4WLnpnie8p9FvOmIUxwl5HvgPJIcFA==
|
||||
@ -15080,7 +15069,7 @@ ember-in-element-polyfill@^1.0.1:
|
||||
ember-cli-htmlbars "^5.3.1"
|
||||
ember-cli-version-checker "^5.1.2"
|
||||
|
||||
ember-in-viewport@4.1.0:
|
||||
ember-in-viewport@4.1.0, ember-in-viewport@~3.10.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-in-viewport/-/ember-in-viewport-4.1.0.tgz#a9359a1e4a99d9d6ab32e926749dc131084ed896"
|
||||
integrity sha512-3y6qWXuJPPc6vX2GfxWgtr+sDjb+bdZF9babstr0lTd8t8c1b42gJ13GaJqlylZIyZz2dEXFCimX9WAeudPv9g==
|
||||
@ -15094,18 +15083,6 @@ ember-in-viewport@4.1.0:
|
||||
intersection-observer-admin "~0.3.2"
|
||||
raf-pool "~0.1.4"
|
||||
|
||||
ember-in-viewport@~3.10.2:
|
||||
version "3.10.3"
|
||||
resolved "https://registry.yarnpkg.com/ember-in-viewport/-/ember-in-viewport-3.10.3.tgz#317472bb82bed11f7895821b799349c6a7406e81"
|
||||
integrity sha512-hSX7p+G6hJjZaY2BAqzyuiMP7QIHzQ4g0+ZBnEwAa8GMbILFAtzPx5A4XEX8wY6dSzhHB9n9jkcWZdmaML6q8A==
|
||||
dependencies:
|
||||
ember-auto-import "^1.11.2"
|
||||
ember-cli-babel "^7.26.3"
|
||||
ember-modifier "^2.1.0"
|
||||
fast-deep-equal "^2.0.1"
|
||||
intersection-observer-admin "~0.3.2"
|
||||
raf-pool "~0.1.4"
|
||||
|
||||
ember-infinity@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-infinity/-/ember-infinity-2.3.0.tgz#73fe13883c212147bfba4f0b2fe8c8d2a96887d9"
|
||||
@ -15208,20 +15185,7 @@ ember-modifier@4.1.0, "ember-modifier@^2.1.2 || ^3.0.0 || ^4.0.0", "ember-modifi
|
||||
ember-cli-normalize-entity-name "^1.0.0"
|
||||
ember-cli-string-utils "^1.1.0"
|
||||
|
||||
ember-modifier@^2.1.0:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-2.1.2.tgz#62d18faedf972dcd9d34f90d5321fbc943d139b1"
|
||||
integrity sha512-3Lsu1fV1sIGa66HOW07RZc6EHISwKt5VA5AUnFss2HX6OTfpxTJ2qvPctt2Yt0XPQXJ4G6BQasr/F35CX7UGJA==
|
||||
dependencies:
|
||||
ember-cli-babel "^7.22.1"
|
||||
ember-cli-normalize-entity-name "^1.0.0"
|
||||
ember-cli-string-utils "^1.1.0"
|
||||
ember-cli-typescript "^3.1.3"
|
||||
ember-compatibility-helpers "^1.2.4"
|
||||
ember-destroyable-polyfill "^2.0.2"
|
||||
ember-modifier-manager-polyfill "^1.2.0"
|
||||
|
||||
ember-modifier@^3.0.0, ember-modifier@^3.2.7:
|
||||
ember-modifier@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/ember-modifier/-/ember-modifier-3.2.7.tgz#f2d35b7c867cbfc549e1acd8d8903c5ecd02ea4b"
|
||||
integrity sha512-ezcPQhH8jUfcJQbbHji4/ZG/h0yyj1jRDknfYue/ypQS8fM8LrGcCMo0rjDZLzL1Vd11InjNs3BD7BdxFlzGoA==
|
||||
@ -15379,14 +15343,6 @@ ember-source@3.24.0:
|
||||
semver "^6.1.1"
|
||||
silent-error "^1.1.1"
|
||||
|
||||
ember-style-modifier@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-style-modifier/-/ember-style-modifier-0.7.0.tgz#85b3dfd7e4bc2bd546df595f2dab4fb141cf7d87"
|
||||
integrity sha512-iDzffiwJcb9j6gu3g8CxzZOTvRZ0BmLMEFl+uyqjiaj72VVND9+HbLyQRw1/ewPAtinhSktxxTTdwU/JO+stLw==
|
||||
dependencies:
|
||||
ember-cli-babel "^7.26.6"
|
||||
ember-modifier "^3.0.0"
|
||||
|
||||
"ember-style-modifier@^0.8.0 || ^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-style-modifier/-/ember-style-modifier-1.0.0.tgz#96e5d342a255d8c1cba1637779dbb1949322e139"
|
||||
@ -15556,6 +15512,11 @@ emoji-regex@^8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
emoji-regex@^9.2.2:
|
||||
version "9.2.2"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
|
||||
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
|
||||
|
||||
emojis-list@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
|
||||
@ -17200,6 +17161,11 @@ flexsearch@0.7.21:
|
||||
resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.7.21.tgz#0f5ede3f2aae67ddc351efbe3b24b69d29e9d48b"
|
||||
integrity sha512-W7cHV7Hrwjid6lWmy0IhsWDFQboWSng25U3VVywpHOTJnnAZNPScog67G+cVpeX9f7yDD21ih0WDrMMT+JoaYg==
|
||||
|
||||
flexsearch@0.7.43:
|
||||
version "0.7.43"
|
||||
resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.7.43.tgz#34f89b36278a466ce379c5bf6fb341965ed3f16c"
|
||||
integrity sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==
|
||||
|
||||
flow-parser@0.*:
|
||||
version "0.205.1"
|
||||
resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.205.1.tgz#337464aaf027b00b2514610386cf21a5f7c94137"
|
||||
@ -19943,12 +19909,12 @@ iterate-value@^1.0.2:
|
||||
es-get-iterator "^1.0.2"
|
||||
iterate-iterator "^1.0.1"
|
||||
|
||||
jackspeak@2.1.1, jackspeak@^2.3.5:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.1.tgz#2a42db4cfbb7e55433c28b6f75d8b796af9669cd"
|
||||
integrity sha512-juf9stUEwUaILepraGOWIJTLwg48bUnBmRqd2ln2Os1sW987zeoj/hzhbvRB95oMuS2ZTpjULmdwHNX4rzZIZw==
|
||||
jackspeak@^2.3.5:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8"
|
||||
integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==
|
||||
dependencies:
|
||||
cliui "^8.0.1"
|
||||
"@isaacs/cliui" "^8.0.2"
|
||||
optionalDependencies:
|
||||
"@pkgjs/parseargs" "^0.11.0"
|
||||
|
||||
@ -28208,6 +28174,15 @@ string-template@~0.2.1:
|
||||
resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
|
||||
integrity sha512-Yptehjogou2xm4UJbxJ4CxgZx12HBfeystp0y3x7s4Dj32ltVVG1Gg8YhKjHZkHicuKpZX/ffilA8505VbUbpw==
|
||||
|
||||
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
|
||||
@ -28217,15 +28192,6 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^1.0.0"
|
||||
strip-ansi "^3.0.0"
|
||||
|
||||
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||
version "4.2.3"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||
dependencies:
|
||||
emoji-regex "^8.0.0"
|
||||
is-fullwidth-code-point "^3.0.0"
|
||||
strip-ansi "^6.0.1"
|
||||
|
||||
string-width@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
|
||||
@ -28234,6 +28200,15 @@ string-width@^2.1.0:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string-width@^5.0.1, string-width@^5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794"
|
||||
integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
|
||||
dependencies:
|
||||
eastasianwidth "^0.2.0"
|
||||
emoji-regex "^9.2.2"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
string-width@^7.0.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a"
|
||||
@ -28314,6 +28289,13 @@ stringify-entities@^2.0.0:
|
||||
is-decimal "^1.0.2"
|
||||
is-hexadecimal "^1.0.0"
|
||||
|
||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||
@ -28335,14 +28317,7 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0:
|
||||
dependencies:
|
||||
ansi-regex "^4.1.0"
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-ansi@^7.1.0:
|
||||
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||
integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
|
||||
@ -30780,6 +30755,15 @@ workerpool@^6.0.2, workerpool@^6.0.3, workerpool@^6.1.5, workerpool@^6.4.0:
|
||||
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544"
|
||||
integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
|
||||
|
||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^6.0.1:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
|
||||
@ -30789,14 +30773,14 @@ wrap-ansi@^6.0.1:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
wrap-ansi@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||
wrap-ansi@^8.1.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||
integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
|
||||
dependencies:
|
||||
ansi-styles "^4.0.0"
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
ansi-styles "^6.1.0"
|
||||
string-width "^5.0.1"
|
||||
strip-ansi "^7.0.1"
|
||||
|
||||
wrap-ansi@^9.0.0:
|
||||
version "9.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user