diff --git a/.eslintrc.json b/.eslintrc.json index ef5bc12588d..47d3a955e97 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -66,7 +66,7 @@ "leadingUnderscore": "allowSingleOrDouble", "trailingUnderscore": "allow", "filter": { - "regex": "(should)|@tags", + "regex": "(should)|@tags|\\d+", "match": false } }, diff --git a/angular.json b/angular.json index 31156dd92f5..e428038adb8 100644 --- a/angular.json +++ b/angular.json @@ -36,7 +36,7 @@ "src/manifest.json", "src/static" ], - "styles": ["src/styles.scss"], + "styles": ["src/styles.scss", "node_modules/@ctrl/ngx-emoji-mart/picker.css"], "scripts": [], "webWorkerTsConfig": "src/tsconfig.worker.json", "browser": "src/main.ts", @@ -205,7 +205,7 @@ "tsConfig": "src/tsconfig.spec.json", "preserveSymlinks": true, "karmaConfig": "src/karma.conf.js", - "styles": ["src/styles.scss"], + "styles": ["src/styles.scss", "node_modules/@ctrl/ngx-emoji-mart/picker.css"], "scripts": [], "assets": ["src/favicon.ico", "src/assets", "src/manifest.json"] } diff --git a/package-lock.json b/package-lock.json index e5bbc39fb7d..173312d5573 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "10.0.11", "license": "MIT", "dependencies": { + "@ctrl/ngx-emoji-mart": "^9.2.0", "electron-dl": "^3.5.2", "electron-localshortcut": "^3.2.1", "electron-log": "^5.1.2", @@ -235,6 +236,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -511,6 +513,8 @@ }, "node_modules/@angular-devkit/build-angular/node_modules/rxjs": { "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -547,6 +551,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -623,6 +628,8 @@ }, "node_modules/@angular-devkit/core/node_modules/rxjs": { "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -652,6 +659,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -1096,6 +1104,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -1294,7 +1303,6 @@ "version": "18.1.4", "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.1.4.tgz", "integrity": "sha512-+N3oWYFubT3GdCkBfD/CmH4DGjr/fGFQZChWbph2ZuPpK7JYNgfyvXS4SjLtdL4WTjjBevBTgR70GyLH/5EbKA==", - "dev": true, "dependencies": { "tslib": "^2.3.0" }, @@ -3372,6 +3380,28 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@ctrl/ngx-emoji-mart": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/ngx-emoji-mart/-/ngx-emoji-mart-9.2.0.tgz", + "integrity": "sha512-q8B7DiXPfyTe+VOGO6Ix7eh5gKCuADxAsud2xXRTlECTfchoKGTmirSczyjaxIKE/xmB+/ZsnkpthZqUM7SAJQ==", + "license": "MIT", + "dependencies": { + "rxjs": "7.8.1", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=15.0.0-0" + } + }, + "node_modules/@ctrl/ngx-emoji-mart/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@ctrl/tinycolor": { "version": "4.1.0", "dev": true, @@ -22139,7 +22169,8 @@ }, "node_modules/rxjs": { "version": "6.6.7", - "dev": true, + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^1.9.0" @@ -22150,7 +22181,8 @@ }, "node_modules/rxjs/node_modules/tslib": { "version": "1.14.1", - "dev": true, + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, "node_modules/sade": { @@ -23981,8 +24013,7 @@ "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "dev": true + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/tuf-js": { "version": "2.2.1", @@ -24601,6 +24632,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } @@ -25575,7 +25607,6 @@ }, "node_modules/zone.js": { "version": "0.14.7", - "dev": true, "license": "MIT" }, "tools/schematics": { diff --git a/package.json b/package.json index 020891c3a81..e2bc3baf1ac 100644 --- a/package.json +++ b/package.json @@ -106,6 +106,7 @@ } ], "dependencies": { + "@ctrl/ngx-emoji-mart": "^9.2.0", "electron-dl": "^3.5.2", "electron-localshortcut": "^3.2.1", "electron-log": "^5.1.2", diff --git a/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.html b/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.html index 644aac2e217..554d8f42ac2 100644 --- a/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.html +++ b/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.html @@ -15,7 +15,17 @@ mat-menu-item > {{ workContext().taskIds.length }} - {{ workContext().icon || defaultIcon() }} + + + {{ workContext().icon || defaultIcon() }} + {{ workContext().title }} diff --git a/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.scss b/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.scss index fba5e15cc93..e2cc6d1caa6 100644 --- a/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.scss +++ b/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.scss @@ -36,6 +36,15 @@ &.isHidden { display: none !important; } + + button.mat-mdc-menu-item { + ngx-emoji { + display: block; + width: 40px; + height: 40px; + top: 5px; + } + } } // color bar left styles diff --git a/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.ts b/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.ts index 2d846e58a1e..6d64810442b 100644 --- a/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.ts +++ b/src/app/core-ui/side-nav/side-nav-item/side-nav-item.component.ts @@ -17,6 +17,8 @@ import { Project } from '../../../features/project/project.model'; import { WorkContextMenuComponent } from '../../work-context-menu/work-context-menu.component'; import { ContextMenuComponent } from '../../../ui/context-menu/context-menu.component'; import { CdkDragPlaceholder } from '@angular/cdk/drag-drop'; +import { EmojiComponent } from '@ctrl/ngx-emoji-mart/ngx-emoji'; +import { CommonModule } from '@angular/common'; @Component({ selector: 'side-nav-item', @@ -28,6 +30,8 @@ import { CdkDragPlaceholder } from '@angular/cdk/drag-drop'; WorkContextMenuComponent, ContextMenuComponent, CdkDragPlaceholder, + EmojiComponent, + CommonModule, ], templateUrl: './side-nav-item.component.html', styleUrl: './side-nav-item.component.scss', diff --git a/src/app/features/config/config.module.ts b/src/app/features/config/config.module.ts index 439ea2d8f51..b1b9e10cbab 100644 --- a/src/app/features/config/config.module.ts +++ b/src/app/features/config/config.module.ts @@ -20,6 +20,8 @@ import { MatSliderModule } from '@angular/material/slider'; import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatInputModule } from '@angular/material/input'; import { TranslateModule } from '@ngx-translate/core'; +import { SkinComponent } from '@ctrl/ngx-emoji-mart'; +import { EmojiComponent } from '@ctrl/ngx-emoji-mart/ngx-emoji'; @NgModule({ imports: [ @@ -62,6 +64,8 @@ import { TranslateModule } from '@ngx-translate/core'; MatSlideToggleModule, MatInputModule, TranslateModule, + EmojiComponent, + SkinComponent, ], declarations: [ ConfigSectionComponent, diff --git a/src/app/features/config/icon-input/icon-input.component.html b/src/app/features/config/icon-input/icon-input.component.html index c25c64a16ab..1b0da3e6493 100644 --- a/src/app/features/config/icon-input/icon-input.component.html +++ b/src/app/features/config/icon-input/icon-input.component.html @@ -1,7 +1,16 @@ -{{ formControl.value }} + + + {{ formControl.value }} + + + + + + + {{emojiId}} + { filteredIcons: string[] = []; + filteredEmojis: string[] = []; + i18n = { + skintones: { + 1: 'Default Skin Tone', + 2: 'Light Skin Tone', + 3: 'Medium-Light Skin Tone', + 4: 'Medium Skin Tone', + 5: 'Medium-Dark Skin Tone', + 6: 'Dark Skin Tone', + }, + }; + chosenSkin = Number(localStorage.getItem('emoji-mart.skin')) || 1; get type(): string { return this.to.type || 'text'; @@ -21,11 +34,15 @@ export class IconInputComponent extends FieldType { } onInputValueChange(val: string): void { - const arr = MATERIAL_ICONS.filter( + const iconsResult = MATERIAL_ICONS.filter( (icoStr) => icoStr && icoStr.toLowerCase().includes(val.toLowerCase()), ); - arr.length = Math.min(150, arr.length); - this.filteredIcons = arr; + iconsResult.length = Math.min(150, iconsResult.length); + this.filteredIcons = iconsResult; + this.filteredEmojis = + this.emojiSearch + .search(val, undefined, 15) + ?.map(({ id }) => `emoji:${id}:${this.chosenSkin}`) || []; if (!val) { this.formControl.setValue(''); @@ -36,6 +53,30 @@ export class IconInputComponent extends FieldType { this.formControl.setValue(icon); } + isEmoji(): boolean { + return Boolean(this.formControl.value?.startsWith('emoji:')); + } + + changeSkin(newSkin: number): void { + this.chosenSkin = newSkin; + if (!this.isEmoji()) { + return; + } + + const currentEmoji = this.formControl.value as string; + if (currentEmoji.lastIndexOf(':') === currentEmoji.indexOf(':')) { + this.formControl.setValue(`${currentEmoji}:${newSkin}`); + } else { + this.formControl.setValue( + currentEmoji.substring(0, currentEmoji.lastIndexOf(':')) + `:${newSkin}`, + ); + } + } + + constructor(private emojiSearch: EmojiSearch) { + super(); + } + // onKeyDown(ev: KeyboardEvent): void { // if (ev.key === 'Enter') { // const ico = (ev as any)?.target?.value; diff --git a/src/app/features/tag/tag.module.ts b/src/app/features/tag/tag.module.ts index f5daf1fb80b..86a1065cd1d 100644 --- a/src/app/features/tag/tag.module.ts +++ b/src/app/features/tag/tag.module.ts @@ -10,6 +10,7 @@ import { TagListComponent } from './tag-list/tag-list.component'; import { DialogEditTagsForTaskComponent } from './dialog-edit-tags/dialog-edit-tags-for-task.component'; import { TagComponent } from './tag/tag.component'; import { IssueModule } from '../issue/issue.module'; +import { EmojiComponent } from '@ctrl/ngx-emoji-mart/ngx-emoji'; @NgModule({ imports: [ @@ -19,6 +20,7 @@ import { IssueModule } from '../issue/issue.module'; StoreModule.forFeature(TAG_FEATURE_NAME, tagReducer), EffectsModule.forFeature([TagEffects]), IssueModule, + EmojiComponent, ], declarations: [ TagListComponent, diff --git a/src/app/features/tag/tag/tag.component.html b/src/app/features/tag/tag/tag.component.html index 6f4dee008fa..0206071ab15 100644 --- a/src/app/features/tag/tag/tag.component.html +++ b/src/app/features/tag/tag/tag.component.html @@ -1,8 +1,18 @@ -{{tag().icon}} + [emoji]="{id: tag().icon.split(':')[1]}" + [skin]="tag().icon.split(':')[2]" + set="apple" + size="15" +> + + {{tag().icon}} + { it('should parse scheduled date using local time zone when unspecified', () => { const t = { ...TASK, - title: '@2024-10-12T13:37', + title: '@2030-10-12T13:37', }; const plannedTimestamp = getPlannedDateTimestampFromShortSyntaxReturnValue(t); expect(checkIfCorrectDateAndTime(plannedTimestamp, 'saturday', 13, 37)).toBeTrue(); diff --git a/src/styles/components/_overwrite-material.scss b/src/styles/components/_overwrite-material.scss index 3a91a3b13af..deb34f99aee 100644 --- a/src/styles/components/_overwrite-material.scss +++ b/src/styles/components/_overwrite-material.scss @@ -125,6 +125,8 @@ mat-icon.mat-icon[svgicon] { white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; + display: flex; + align-items: center; } // CDK DRAG