mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-05-12 17:04:57 +00:00
[ACS-7338] Simple search input component (#9454)
* feat(content-services): simple search input component for applications * feat(content-services): support i18n for search input * feat(content-services): apply review suggestions * feat(content-services): apply review suggestions * chore: fix husky install
This commit is contained in:
parent
adec3e60d4
commit
aab03cc864
70
docs/content-services/components/search-input.component.md
Normal file
70
docs/content-services/components/search-input.component.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Search Input
|
||||||
|
|
||||||
|
`Component`, `Standalone`
|
||||||
|
|
||||||
|
A minimalistic search input component that formats user query according to the provided fields.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<app-search-input
|
||||||
|
[fields]="['cm:name']"
|
||||||
|
(changed)="onSearchQueryChanged($event)">
|
||||||
|
</app-search-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
> Notes: this component does not perform search operations.
|
||||||
|
> It handles the user input, formats and produces the search query to use with `Search Query Builder` or other services.
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
- `fields` **string[]** - optional, a list of fields to use in the formatted search query, defaults to `[cm:name]`
|
||||||
|
- `value` **string** - optional, initial input value
|
||||||
|
- `label` **string** - optional, display label
|
||||||
|
- `placeholder` **string** - optional, display placeholder
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
- `changed` **EventEmitter\<string\>**: emits when user presses `Enter` or moves the focus out of the input area
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```html
|
||||||
|
<app-search-input
|
||||||
|
[fields]="['cm:name', 'cm:title', 'cm:description', 'TEXT', 'TAG']"
|
||||||
|
(changed)="onSearchQueryChanged($event)">
|
||||||
|
</app-search-input>
|
||||||
|
```
|
||||||
|
|
||||||
|
In the example above, the search is performed against the following fields:
|
||||||
|
`cm:name`, `cm:title`, `cm:description`, `TEXT` and `TAG`.
|
||||||
|
|
||||||
|
The Search Input is going to produce the following results for user inputs:
|
||||||
|
|
||||||
|
user types `test`
|
||||||
|
|
||||||
|
```text
|
||||||
|
(cm:name:"test*" OR cm:title:"test*" OR cm:description:"test*" OR TEXT:"test*" OR TAG:"test*")
|
||||||
|
```
|
||||||
|
|
||||||
|
user types `*`
|
||||||
|
|
||||||
|
```text
|
||||||
|
(cm:name:"**" OR cm:title:"**" OR cm:description:"**" OR TEXT:"**" OR TAG:"**")
|
||||||
|
```
|
||||||
|
|
||||||
|
user types `one two`
|
||||||
|
|
||||||
|
```text
|
||||||
|
(cm:name:"one*" OR cm:title:"one*" OR cm:description:"one*" OR TEXT:"one*" OR TAG:"one*") AND (cm:name:"two*" OR cm:title:"two*" OR cm:description:"two*" OR TEXT:"two*" OR TAG:"two*")
|
||||||
|
```
|
||||||
|
|
||||||
|
user types `one AND two`
|
||||||
|
|
||||||
|
```text
|
||||||
|
(cm:name:"one*" OR cm:title:"one*" OR cm:description:"one*" OR TEXT:"one*" OR TAG:"one*") AND (cm:name:"two*" OR cm:title:"two*" OR cm:description:"two*" OR TEXT:"two*" OR TAG:"two*")
|
||||||
|
```
|
||||||
|
|
||||||
|
user types `one OR two`
|
||||||
|
|
||||||
|
```text
|
||||||
|
(cm:name:"one*" OR cm:title:"one*" OR cm:description:"one*" OR TEXT:"one*" OR TAG:"one*") OR (cm:name:"two*" OR cm:title:"two*" OR cm:description:"two*" OR TEXT:"two*" OR TAG:"two*")
|
||||||
|
```
|
2
lib/content-services/.eslintignore
Normal file
2
lib/content-services/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.storybook
|
||||||
|
coverage
|
@ -278,6 +278,8 @@
|
|||||||
"ARIA-LABEL": "Search button"
|
"ARIA-LABEL": "Search button"
|
||||||
},
|
},
|
||||||
"INPUT": {
|
"INPUT": {
|
||||||
|
"LABEL": "Search",
|
||||||
|
"PLACEHOLDER": "Search Query",
|
||||||
"ARIA-LABEL": "Search input"
|
"ARIA-LABEL": "Search input"
|
||||||
},
|
},
|
||||||
"RESULTS": {
|
"RESULTS": {
|
||||||
|
@ -27,6 +27,7 @@ export * from './search-filter-chips';
|
|||||||
export * from './search-filter-container';
|
export * from './search-filter-container';
|
||||||
export * from './search-filter-tabbed';
|
export * from './search-filter-tabbed';
|
||||||
export * from './search-form';
|
export * from './search-form';
|
||||||
|
export * from './search-input';
|
||||||
export * from './search-logical-filter';
|
export * from './search-logical-filter';
|
||||||
export * from './search-number-range';
|
export * from './search-number-range';
|
||||||
export * from './search-panel';
|
export * from './search-panel';
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export * from './search-input.component';
|
@ -0,0 +1,6 @@
|
|||||||
|
<div class="adf-search-input-container">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label *ngIf="label">{{ label | translate }}</mat-label>
|
||||||
|
<input matInput [placeholder]="placeholder | translate" [value]="value" (change)="onSearchInputChanged($event)" />
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
@ -0,0 +1,12 @@
|
|||||||
|
/* stylelint-disable selector-class-pattern */
|
||||||
|
.adf-search-input-container {
|
||||||
|
margin: 10px;
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.mat-form-field-wrapper {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,113 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MatInputHarness } from '@angular/material/input/testing';
|
||||||
|
import { SearchInputComponent } from '@alfresco/adf-content-services';
|
||||||
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ContentTestingModule } from '../../../testing/content.testing.module';
|
||||||
|
|
||||||
|
describe('SearchInputComponent', () => {
|
||||||
|
let loader: HarnessLoader;
|
||||||
|
let component: SearchInputComponent;
|
||||||
|
let fixture: ComponentFixture<SearchInputComponent>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the search input value
|
||||||
|
*
|
||||||
|
* @param value the value to set
|
||||||
|
*/
|
||||||
|
async function setInputValue(value: string) {
|
||||||
|
const input = await loader.getHarness(MatInputHarness);
|
||||||
|
await input.setValue(value);
|
||||||
|
await (await input.host()).dispatchEvent('change');
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot(), ContentTestingModule, SearchInputComponent]
|
||||||
|
});
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SearchInputComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
|
||||||
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show custom placeholder', async () => {
|
||||||
|
component.placeholder = 'custom placeholder';
|
||||||
|
|
||||||
|
const input = await loader.getHarness(MatInputHarness);
|
||||||
|
const placeholder = await input.getPlaceholder();
|
||||||
|
expect(placeholder).toBe('custom placeholder');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use multiple fields', async () => {
|
||||||
|
component.fields = ['cm:description', 'TAG'];
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
let formatted = '';
|
||||||
|
component.changed.subscribe((val) => (formatted = val));
|
||||||
|
|
||||||
|
await setInputValue('test');
|
||||||
|
|
||||||
|
expect(formatted).toBe('(cm:description:"test*" OR TAG:"test*")');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should emit changed event with [cm:name]', async () => {
|
||||||
|
let formatted = '';
|
||||||
|
component.changed.subscribe((val) => (formatted = val));
|
||||||
|
|
||||||
|
await setInputValue('test');
|
||||||
|
|
||||||
|
expect(formatted).toBe('(cm:name:"test*")');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format with AND by default', async () => {
|
||||||
|
let formatted = '';
|
||||||
|
component.changed.subscribe((val) => (formatted = val));
|
||||||
|
|
||||||
|
await setInputValue('one two');
|
||||||
|
|
||||||
|
expect(formatted).toBe('(cm:name:"one*") AND (cm:name:"two*")');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format with OR if specified directly', async () => {
|
||||||
|
let formatted = '';
|
||||||
|
component.changed.subscribe((val) => (formatted = val));
|
||||||
|
|
||||||
|
await setInputValue('one OR two');
|
||||||
|
|
||||||
|
expect(formatted).toBe('(cm:name:"one*") OR (cm:name:"two*")');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should format with AND if specified directly', async () => {
|
||||||
|
let formatted = '';
|
||||||
|
component.changed.subscribe((val) => (formatted = val));
|
||||||
|
|
||||||
|
await setInputValue('one AND two');
|
||||||
|
|
||||||
|
expect(formatted).toBe('(cm:name:"one*") AND (cm:name:"two*")');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,105 @@
|
|||||||
|
/*!
|
||||||
|
* @license
|
||||||
|
* Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-search-input',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, MatFormFieldModule, MatInputModule, TranslateModule],
|
||||||
|
templateUrl: `./search-input.component.html`,
|
||||||
|
styleUrls: ['./search-input.component.scss'],
|
||||||
|
encapsulation: ViewEncapsulation.None
|
||||||
|
})
|
||||||
|
export class SearchInputComponent {
|
||||||
|
@Input()
|
||||||
|
value = '';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
label = 'SEARCH.INPUT.LABEL';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
placeholder = 'SEARCH.INPUT.PLACEHOLDER';
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
fields = ['cm:name'];
|
||||||
|
|
||||||
|
@Output()
|
||||||
|
changed = new EventEmitter<string>();
|
||||||
|
|
||||||
|
onSearchInputChanged(event: Event) {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
const searchTerm = input.value;
|
||||||
|
|
||||||
|
const query = this.formatSearchQuery(searchTerm, this.fields);
|
||||||
|
if (query) {
|
||||||
|
this.changed.emit(decodeURIComponent(query));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatSearchQuery(userInput: string, fields = ['cm:name']): string {
|
||||||
|
if (!userInput) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^https?:\/\//.test(userInput)) {
|
||||||
|
return this.formatFields(fields, userInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
userInput = userInput.trim();
|
||||||
|
|
||||||
|
if (userInput.includes(':') || userInput.includes('"')) {
|
||||||
|
return userInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
const words = userInput.split(' ');
|
||||||
|
|
||||||
|
if (words.length > 1) {
|
||||||
|
const separator = words.some(this.isOperator) ? ' ' : ' AND ';
|
||||||
|
return words.map((term) => (this.isOperator(term) ? term : this.formatFields(fields, term))).join(separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.formatFields(fields, userInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isOperator(input: string): boolean {
|
||||||
|
if (input) {
|
||||||
|
input = input.trim().toUpperCase();
|
||||||
|
|
||||||
|
const operators = ['AND', 'OR'];
|
||||||
|
return operators.includes(input);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatFields(fields: string[], term: string): string {
|
||||||
|
let prefix = '';
|
||||||
|
let suffix = '*';
|
||||||
|
|
||||||
|
if (term.startsWith('=')) {
|
||||||
|
prefix = '=';
|
||||||
|
suffix = '';
|
||||||
|
term = term.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '(' + fields.map((field) => `${prefix}${field}:"${term}${suffix}"`).join(' OR ') + ')';
|
||||||
|
}
|
||||||
|
}
|
@ -58,6 +58,7 @@ import { SearchDateRangeTabbedComponent } from './components/search-date-range-t
|
|||||||
import { SearchFilterTabDirective } from './components/search-filter-tabbed/search-filter-tab.directive';
|
import { SearchFilterTabDirective } from './components/search-filter-tabbed/search-filter-tab.directive';
|
||||||
import { SearchFacetChipTabbedComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
|
import { SearchFacetChipTabbedComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
|
||||||
import { SearchFacetTabbedContentComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-tabbed-content.component';
|
import { SearchFacetTabbedContentComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-tabbed-content.component';
|
||||||
|
import { SearchInputComponent } from './components/search-input';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -67,7 +68,8 @@ import { SearchFacetTabbedContentComponent } from './components/search-filter-ch
|
|||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MaterialModule,
|
MaterialModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
SearchTextModule
|
SearchTextModule,
|
||||||
|
SearchInputComponent
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
SearchComponent,
|
SearchComponent,
|
||||||
|
1
lib/core/.eslintignore
Normal file
1
lib/core/.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
.storybook
|
1
lib/process-services-cloud/.eslintignore
Normal file
1
lib/process-services-cloud/.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
.storybook
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -7,7 +7,6 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "alfresco-ng2-components",
|
"name": "alfresco-ng2-components",
|
||||||
"version": "6.7.1",
|
"version": "6.7.1",
|
||||||
"hasInstallScript": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "14.1.3",
|
"@angular/animations": "14.1.3",
|
||||||
@ -34311,8 +34310,9 @@
|
|||||||
},
|
},
|
||||||
"node_modules/husky": {
|
"node_modules/husky": {
|
||||||
"version": "7.0.4",
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"husky": "lib/bin.js"
|
"husky": "lib/bin.js"
|
||||||
},
|
},
|
||||||
|
12
package.json
12
package.json
@ -5,7 +5,7 @@
|
|||||||
"version": "6.7.1",
|
"version": "6.7.1",
|
||||||
"author": "Hyland Software, Inc. and its affiliates",
|
"author": "Hyland Software, Inc. and its affiliates",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "node ./decorate-angular-cli.js && ngcc --properties es2015 browser module main",
|
"prepare": "husky install",
|
||||||
"ng": "nx",
|
"ng": "nx",
|
||||||
"00": "echo -------------------------------------------- DOC -----------------------------------------------",
|
"00": "echo -------------------------------------------- DOC -----------------------------------------------",
|
||||||
"build-doc-tools": "tsc -p ./tools/doc/tsconfig.json",
|
"build-doc-tools": "tsc -p ./tools/doc/tsconfig.json",
|
||||||
@ -99,17 +99,13 @@
|
|||||||
"@angular-eslint/template-parser": "16.2.0",
|
"@angular-eslint/template-parser": "16.2.0",
|
||||||
"@angular/cli": "~14.2.12",
|
"@angular/cli": "~14.2.12",
|
||||||
"@angular/compiler-cli": "14.1.3",
|
"@angular/compiler-cli": "14.1.3",
|
||||||
"@editorjs/editorjs": "^2.29.0",
|
|
||||||
"@editorjs/code": "2.9.0",
|
"@editorjs/code": "2.9.0",
|
||||||
|
"@editorjs/editorjs": "^2.29.0",
|
||||||
"@editorjs/header": "2.8.1",
|
"@editorjs/header": "2.8.1",
|
||||||
"@editorjs/inline-code": "1.5.0",
|
"@editorjs/inline-code": "1.5.0",
|
||||||
"@editorjs/list": "1.9.0",
|
"@editorjs/list": "1.9.0",
|
||||||
"@editorjs/marker": "1.4.0",
|
"@editorjs/marker": "1.4.0",
|
||||||
"@editorjs/underline": "1.1.0",
|
"@editorjs/underline": "1.1.0",
|
||||||
"editorjs-text-color-plugin": "2.0.4",
|
|
||||||
"editorjs-html": "3.4.3",
|
|
||||||
"editorjs-paragraph-with-alignment": "3.0.0",
|
|
||||||
"@quanzo/change-font-size": "1.0.0",
|
|
||||||
"@nrwl/angular": "14.8.9",
|
"@nrwl/angular": "14.8.9",
|
||||||
"@nrwl/cli": "14.8.9",
|
"@nrwl/cli": "14.8.9",
|
||||||
"@nrwl/eslint-plugin-nx": "14.5.4",
|
"@nrwl/eslint-plugin-nx": "14.5.4",
|
||||||
@ -118,6 +114,7 @@
|
|||||||
"@nrwl/workspace": "14.8.9",
|
"@nrwl/workspace": "14.8.9",
|
||||||
"@paperist/types-remark": "0.1.3",
|
"@paperist/types-remark": "0.1.3",
|
||||||
"@playwright/test": "^1.35.1",
|
"@playwright/test": "^1.35.1",
|
||||||
|
"@quanzo/change-font-size": "1.0.0",
|
||||||
"@storybook/addon-essentials": "6.5.10",
|
"@storybook/addon-essentials": "6.5.10",
|
||||||
"@storybook/angular": "6.5.16",
|
"@storybook/angular": "6.5.16",
|
||||||
"@storybook/builder-webpack5": "6.5.10",
|
"@storybook/builder-webpack5": "6.5.10",
|
||||||
@ -141,6 +138,9 @@
|
|||||||
"commander": "6.2.1",
|
"commander": "6.2.1",
|
||||||
"css-loader": "^6.10.0",
|
"css-loader": "^6.10.0",
|
||||||
"dotenv": "16.1.3",
|
"dotenv": "16.1.3",
|
||||||
|
"editorjs-html": "3.4.3",
|
||||||
|
"editorjs-paragraph-with-alignment": "3.0.0",
|
||||||
|
"editorjs-text-color-plugin": "2.0.4",
|
||||||
"ejs": "^3.1.9",
|
"ejs": "^3.1.9",
|
||||||
"eslint": "^8.47.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-config-prettier": "^8.10.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user