Merge pull request #142 from NicolasConstant/topic_emoji-picker
Topic emoji picker
This commit is contained in:
commit
2dea5544eb
@ -21,7 +21,8 @@
|
||||
"src/favicon.ico"
|
||||
],
|
||||
"styles": [
|
||||
"src/sass/styles.scss"
|
||||
"src/sass/styles.scss",
|
||||
"node_modules/@ctrl/ngx-emoji-mart/picker.css"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
|
10
package-lock.json
generated
10
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sengi",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -711,6 +711,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@ctrl/ngx-emoji-mart": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/ngx-emoji-mart/-/ngx-emoji-mart-0.17.0.tgz",
|
||||
"integrity": "sha512-gdHM/OPTbqWMIlFPAbjgAPo5BGsjkehILCInw5OttuT25HMZXJFjWVpi6vGixNVrAs8kz6sTYM/wbldS5GP9yQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"@fortawesome/angular-fontawesome": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.3.0.tgz",
|
||||
|
@ -37,6 +37,7 @@
|
||||
"@angular/platform-browser": "^7.2.7",
|
||||
"@angular/platform-browser-dynamic": "^7.2.7",
|
||||
"@angular/router": "^7.2.7",
|
||||
"@ctrl/ngx-emoji-mart": "^0.17.0",
|
||||
"@fortawesome/angular-fontawesome": "^0.3.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.13",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.7.0",
|
||||
|
@ -9,9 +9,11 @@ import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
import { NgxsModule } from '@ngxs/store';
|
||||
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
|
||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
|
||||
import { ContextMenuModule } from 'ngx-contextmenu';
|
||||
import { PickerModule } from '@ctrl/ngx-emoji-mart';
|
||||
|
||||
import { AppComponent } from "./app.component";
|
||||
import { LeftSideBarComponent } from "./components/left-side-bar/left-side-bar.component";
|
||||
@ -64,78 +66,86 @@ import { ListAccountComponent } from './components/floating-column/manage-accoun
|
||||
import { PollComponent } from './components/stream/status/poll/poll.component';
|
||||
import { TimeLeftPipe } from './pipes/time-left.pipe';
|
||||
import { AutosuggestComponent } from './components/create-status/autosuggest/autosuggest.component';
|
||||
import { EmojiPickerComponent } from './components/create-status/emoji-picker/emoji-picker.component';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", redirectTo: "home", pathMatch: "full" },
|
||||
{ path: "home", component: StreamsMainDisplayComponent },
|
||||
{ path: "register", component: RegisterNewAccountComponent},
|
||||
{ path: "**", redirectTo: "home" }
|
||||
{ path: "", redirectTo: "home", pathMatch: "full" },
|
||||
{ path: "home", component: StreamsMainDisplayComponent },
|
||||
{ path: "register", component: RegisterNewAccountComponent },
|
||||
{ path: "**", redirectTo: "home" }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
LeftSideBarComponent,
|
||||
StreamsMainDisplayComponent,
|
||||
StreamComponent,
|
||||
StreamsSelectionFooterComponent,
|
||||
StatusComponent,
|
||||
RegisterNewAccountComponent,
|
||||
AccountIconComponent,
|
||||
FloatingColumnComponent,
|
||||
ManageAccountComponent,
|
||||
AddNewStatusComponent,
|
||||
AttachementsComponent,
|
||||
SettingsComponent,
|
||||
AddNewAccountComponent,
|
||||
SearchComponent,
|
||||
ActionBarComponent,
|
||||
WaitingAnimationComponent,
|
||||
UserProfileComponent,
|
||||
ThreadComponent,
|
||||
HashtagComponent,
|
||||
StreamOverlayComponent,
|
||||
DatabindedTextComponent,
|
||||
TimeAgoPipe,
|
||||
StreamStatusesComponent,
|
||||
StreamEditionComponent,
|
||||
TutorialComponent,
|
||||
NotificationHubComponent,
|
||||
MediaViewerComponent,
|
||||
CreateStatusComponent,
|
||||
MediaComponent,
|
||||
MyAccountComponent,
|
||||
FavoritesComponent,
|
||||
DirectMessagesComponent,
|
||||
MentionsComponent,
|
||||
NotificationsComponent,
|
||||
AccountEmojiPipe,
|
||||
CardComponent,
|
||||
ListEditorComponent,
|
||||
ListAccountComponent,
|
||||
PollComponent,
|
||||
TimeLeftPipe,
|
||||
AutosuggestComponent
|
||||
],
|
||||
imports: [
|
||||
FontAwesomeModule,
|
||||
BrowserModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
RouterModule.forRoot(routes),
|
||||
declarations: [
|
||||
AppComponent,
|
||||
LeftSideBarComponent,
|
||||
StreamsMainDisplayComponent,
|
||||
StreamComponent,
|
||||
StreamsSelectionFooterComponent,
|
||||
StatusComponent,
|
||||
RegisterNewAccountComponent,
|
||||
AccountIconComponent,
|
||||
FloatingColumnComponent,
|
||||
ManageAccountComponent,
|
||||
AddNewStatusComponent,
|
||||
AttachementsComponent,
|
||||
SettingsComponent,
|
||||
AddNewAccountComponent,
|
||||
SearchComponent,
|
||||
ActionBarComponent,
|
||||
WaitingAnimationComponent,
|
||||
UserProfileComponent,
|
||||
ThreadComponent,
|
||||
HashtagComponent,
|
||||
StreamOverlayComponent,
|
||||
DatabindedTextComponent,
|
||||
TimeAgoPipe,
|
||||
StreamStatusesComponent,
|
||||
StreamEditionComponent,
|
||||
TutorialComponent,
|
||||
NotificationHubComponent,
|
||||
MediaViewerComponent,
|
||||
CreateStatusComponent,
|
||||
MediaComponent,
|
||||
MyAccountComponent,
|
||||
FavoritesComponent,
|
||||
DirectMessagesComponent,
|
||||
MentionsComponent,
|
||||
NotificationsComponent,
|
||||
AccountEmojiPipe,
|
||||
CardComponent,
|
||||
ListEditorComponent,
|
||||
ListAccountComponent,
|
||||
PollComponent,
|
||||
TimeLeftPipe,
|
||||
AutosuggestComponent,
|
||||
EmojiPickerComponent
|
||||
],
|
||||
entryComponents: [
|
||||
EmojiPickerComponent
|
||||
],
|
||||
imports: [
|
||||
FontAwesomeModule,
|
||||
BrowserModule,
|
||||
HttpModule,
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
PickerModule,
|
||||
OverlayModule,
|
||||
RouterModule.forRoot(routes),
|
||||
|
||||
NgxsModule.forRoot([
|
||||
RegisteredAppsState,
|
||||
AccountsState,
|
||||
StreamsState,
|
||||
SettingsState
|
||||
]),
|
||||
NgxsStoragePluginModule.forRoot(),
|
||||
ContextMenuModule.forRoot()
|
||||
],
|
||||
providers: [AuthService, NavigationService, NotificationService, MastodonService, StreamingService],
|
||||
bootstrap: [AppComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
NgxsModule.forRoot([
|
||||
RegisteredAppsState,
|
||||
AccountsState,
|
||||
StreamsState,
|
||||
SettingsState
|
||||
]),
|
||||
NgxsStoragePluginModule.forRoot(),
|
||||
ContextMenuModule.forRoot()
|
||||
],
|
||||
providers: [AuthService, NavigationService, NotificationService, MastodonService, StreamingService],
|
||||
bootstrap: [AppComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class AppModule { }
|
||||
|
@ -2,12 +2,18 @@
|
||||
<input [(ngModel)]="title" type="text" class="form-control form-control-sm status-editor__title" name="title"
|
||||
autocomplete="off" placeholder="Title, Content Warning (optional)" title="title, content warning (optional)" />
|
||||
|
||||
<textarea #reply [(ngModel)]="status" name="status"
|
||||
class="form-control form-control-sm status-editor__content" rows="5" required
|
||||
title="content" placeholder="What's in your mind?" (keydown.control.enter)="onCtrlEnter()"
|
||||
(keydown)="handleKeyDown($event)" (blur)="statusTextEditorLostFocus()"></textarea>
|
||||
<a class="status-editor__emoji" title="Insert Emoji"
|
||||
#emojiButton href (click)="openEmojiPicker($event)">
|
||||
<img class="status-editor__emoji--image" src="/assets/emoji/72x72/1f636.png">
|
||||
</a>
|
||||
|
||||
<div class="status-editor__mention-error" *ngIf="mentionTooFarAwayError">Error: mentions must be placed closer to the
|
||||
<textarea #reply [(ngModel)]="status" name="status" class="form-control form-control-sm status-editor__content"
|
||||
rows="5" required title="content" placeholder="What's in your mind?" (keydown.control.enter)="onCtrlEnter()"
|
||||
(keydown)="handleKeyDown($event)" (blur)="statusTextEditorLostFocus()">
|
||||
</textarea>
|
||||
|
||||
<div class="status-editor__mention-error" *ngIf="mentionTooFarAwayError">Error: mentions must be placed closer to
|
||||
the
|
||||
start in order to use multiposting.</div>
|
||||
|
||||
<app-autosuggest class="status-editor__autosuggest" *ngIf="autosuggestData" [pattern]="autosuggestData"
|
||||
@ -43,7 +49,6 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<context-menu #contextMenu>
|
||||
<ng-template contextMenuItem (execute)="changePrivacy('Public')">
|
||||
<fa-icon [icon]="faGlobeAmericas" class="context-menu-icon"></fa-icon> Public
|
||||
|
@ -6,6 +6,8 @@
|
||||
$btn-send-status-width: 60px;
|
||||
$counter-width: 90px;
|
||||
|
||||
// @import "~@ctrl/ngx-emoji-mart/picker";
|
||||
|
||||
.form-control {
|
||||
margin: 0 0 5px 5px;
|
||||
width: calc(100% - 10px);
|
||||
@ -38,6 +40,35 @@ $counter-width: 90px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__emoji {
|
||||
position: absolute;
|
||||
top: 37px;
|
||||
right: 10px;
|
||||
|
||||
&--image {
|
||||
transition: all .2s;
|
||||
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
|
||||
-webkit-filter: grayscale(100%);
|
||||
-moz-filter: grayscale(100%);
|
||||
-ms-filter: grayscale(100%);
|
||||
-o-filter: grayscale(100%);
|
||||
filter: gray;
|
||||
opacity: .7;
|
||||
|
||||
&:hover {
|
||||
filter: none;
|
||||
-webkit-filter: grayscale(0%);
|
||||
-moz-filter: grayscale(0%);
|
||||
-ms-filter: grayscale(0%);
|
||||
-o-filter: grayscale(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
border-width: 0;
|
||||
background-color: $status-editor-background;
|
||||
@ -57,6 +88,7 @@ $counter-width: 90px;
|
||||
height: 110px;
|
||||
|
||||
padding-bottom: 10px;
|
||||
padding-right: 30px;
|
||||
//border-bottom: 1px solid black;
|
||||
|
||||
&::-webkit-resizer {
|
||||
@ -138,8 +170,8 @@ $counter-width: 90px;
|
||||
width: $btn-send-status-width;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: 5px;
|
||||
font-weight: 500;
|
||||
left: 5px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.context-menu-icon {
|
||||
@ -147,4 +179,21 @@ $counter-width: 90px;
|
||||
left: -3px;
|
||||
font-size: 12px;
|
||||
color: #1f1f1f;
|
||||
}
|
||||
}
|
||||
|
||||
.emojipicker {
|
||||
|
||||
font-size: $default-font-size !important;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@import '~@angular/cdk/overlay-prebuilt.css';
|
||||
// ::ng-deep .cdk-overlay-backdrop {
|
||||
// // width: 100%;
|
||||
// // height: 100%;
|
||||
// border: 3px solid greenyellow;
|
||||
// background-color: black;
|
||||
// min-height: 20px;
|
||||
// }
|
@ -50,6 +50,57 @@ describe('CreateStatusComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not count emoji as multiple chars', () => {
|
||||
const status = '😃 😍 👌 👇 😱 😶 status with 😱 😶 emojis 😏 👍 ';
|
||||
(<any>component).maxCharLength = 500;
|
||||
(<any>component).countStatusChar(status);
|
||||
expect((<any>component).charCountLeft).toBe(461);
|
||||
});
|
||||
|
||||
it('should not count emoji in CW as multiple chars', () => {
|
||||
const status = 'test';
|
||||
(<any>component).title = '🙂 test';
|
||||
(<any>component).maxCharLength = 500;
|
||||
(<any>component).countStatusChar(status);
|
||||
expect((<any>component).charCountLeft).toBe(490);
|
||||
});
|
||||
|
||||
it('should not count domain chars in username', () => {
|
||||
const status = 'dsqdqs @NicolasConstant@mastodon.partipirate.org dsqdqsdqsd';
|
||||
(<any>component).maxCharLength = 500;
|
||||
(<any>component).countStatusChar(status);
|
||||
expect((<any>component).charCountLeft).toBe(466);
|
||||
});
|
||||
|
||||
it('should not count https link more than the minimum', () => {
|
||||
const status = "https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/";
|
||||
(<any>component).maxCharLength = 500;
|
||||
(<any>component).countStatusChar(status);
|
||||
expect((<any>component).charCountLeft).toBe(477);
|
||||
});
|
||||
|
||||
it('should not count http link more than the minimum', () => {
|
||||
const status = "http://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/";
|
||||
(<any>component).maxCharLength = 500;
|
||||
(<any>component).countStatusChar(status);
|
||||
expect((<any>component).charCountLeft).toBe(477);
|
||||
});
|
||||
|
||||
it('should not count links more than the minimum', () => {
|
||||
const status = "http://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ http://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ http://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/";
|
||||
(<any>component).maxCharLength = 500;
|
||||
(<any>component).countStatusChar(status);
|
||||
expect((<any>component).charCountLeft).toBe(429);
|
||||
});
|
||||
|
||||
it('should count correctly complex status', () => {
|
||||
const status = 'dsqdqs @NicolasConstant@mastodon.partipirate.org dsqdqs👇😱 😶 status https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ #Pleroma with 😱 😶 emojis 😏 👍 #Mastodon @ddqsdqs @dsqdsq@dqsdsqqdsq';
|
||||
(<any>component).title = '🙂 test';
|
||||
(<any>component).maxCharLength = 500;
|
||||
(<any>component).countStatusChar(status);
|
||||
expect((<any>component).charCountLeft).toBe(373);
|
||||
});
|
||||
|
||||
it('should not parse small status', () => {
|
||||
const status = 'this is a cool status';
|
||||
(<any>component).maxCharLength = 500;
|
||||
@ -134,4 +185,13 @@ describe('CreateStatusComponent', () => {
|
||||
expect(result[1]).toContain('@Lorem@ipsum.com ');
|
||||
});
|
||||
|
||||
it('should parse long link properly for multiposting', () => {
|
||||
const status = 'dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd qsd sqd qsd qsd dsqd qsd qsd sqd qsd sqd dsq http://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/';
|
||||
(<any>component).maxCharLength = 500;
|
||||
const result = <string[]>(<any>component).parseStatus(status);
|
||||
expect(result.length).toBe(2);
|
||||
expect(result[0].length).toBeLessThanOrEqual(527);
|
||||
expect(result[1].length).toBeLessThanOrEqual(527);
|
||||
expect(result[1]).toBe('http://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/');
|
||||
});
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, ViewChild, ViewContainerRef, ComponentRef, HostListener } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { Subscription, Observable } from 'rxjs';
|
||||
@ -16,6 +16,9 @@ import { AccountInfo } from '../../states/accounts.state';
|
||||
import { InstancesInfoService } from '../../services/instances-info.service';
|
||||
import { MediaService } from '../../services/media.service';
|
||||
import { AutosuggestSelection, AutosuggestUserActionEnum } from './autosuggest/autosuggest.component';
|
||||
import { Overlay, OverlayConfig, FullscreenOverlayContainer, OverlayRef } from '@angular/cdk/overlay';
|
||||
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
|
||||
import { EmojiPickerComponent } from './emoji-picker/emoji-picker.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-status',
|
||||
@ -44,18 +47,13 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
private _status: string = '';
|
||||
@Input('status')
|
||||
set status(value: string) {
|
||||
if (value) {
|
||||
this.countStatusChar(value);
|
||||
this.detectAutosuggestion(value);
|
||||
this._status = value;
|
||||
this.countStatusChar(value);
|
||||
this.detectAutosuggestion(value);
|
||||
this._status = value;
|
||||
|
||||
setTimeout(() => {
|
||||
this.autoGrow();
|
||||
}, 0);
|
||||
|
||||
} else {
|
||||
this.autosuggestData = null;
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.autoGrow();
|
||||
}, 0);
|
||||
}
|
||||
get status(): string {
|
||||
return this._status;
|
||||
@ -155,7 +153,9 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly instancesInfoService: InstancesInfoService,
|
||||
private readonly mediaService: MediaService) {
|
||||
private readonly mediaService: MediaService,
|
||||
private readonly overlay: Overlay,
|
||||
public viewContainerRef: ViewContainerRef) {
|
||||
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
|
||||
}
|
||||
|
||||
@ -186,6 +186,8 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.statusLoaded = true;
|
||||
this.focus();
|
||||
|
||||
this.innerHeight = window.innerHeight;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -353,8 +355,9 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
|
||||
const currentStatus = parseStatus[parseStatus.length - 1];
|
||||
const statusExtraChars = this.getMentionExtraChars(status);
|
||||
const linksExtraChars = this.getLinksExtraChars(status);
|
||||
|
||||
const statusLength = currentStatus.length - statusExtraChars;
|
||||
const statusLength = [...currentStatus].length - statusExtraChars - linksExtraChars;
|
||||
this.charCountLeft = this.maxCharLength - statusLength - this.getCwLength();
|
||||
this.postCounts = parseStatus.length;
|
||||
}
|
||||
@ -362,7 +365,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
private getCwLength(): number {
|
||||
let cwLength = 0;
|
||||
if (this.title) {
|
||||
cwLength = this.title.length;
|
||||
cwLength = [...this.title].length;
|
||||
}
|
||||
return cwLength;
|
||||
}
|
||||
@ -504,6 +507,18 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
return results;
|
||||
}
|
||||
|
||||
private getLinksExtraChars(status: string): number {
|
||||
let mentionExtraChars = 0;
|
||||
let links = status.split(' ').filter(x => x.startsWith('http://') || x.startsWith('https://'));
|
||||
for (let link of links) {
|
||||
if(link.length > 23){
|
||||
mentionExtraChars += link.length - 23;
|
||||
}
|
||||
}
|
||||
|
||||
return mentionExtraChars;
|
||||
}
|
||||
|
||||
private getMentionExtraChars(status: string): number {
|
||||
let mentionExtraChars = 0;
|
||||
let mentions = this.getMentionsFromStatus(status);
|
||||
@ -591,7 +606,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private autoGrow() {
|
||||
let scrolling = (this.replyElement.nativeElement.scrollHeight);
|
||||
let scrolling = (this.replyElement.nativeElement.scrollHeight);
|
||||
|
||||
if (scrolling > 110) {
|
||||
this.replyElement.nativeElement.style.height = `0px`;
|
||||
@ -609,4 +624,70 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
//https://stackblitz.com/edit/overlay-demo
|
||||
@ViewChild('emojiButton') emojiButtonElement: ElementRef;
|
||||
overlayRef: OverlayRef;
|
||||
|
||||
public innerHeight: number;
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event) {
|
||||
this.innerHeight = window.innerHeight;
|
||||
}
|
||||
|
||||
private emojiCloseSub: Subscription;
|
||||
private emojiSelectedSub: Subscription;
|
||||
private beforeEmojiCaretPosition: number;
|
||||
openEmojiPicker(e: MouseEvent): boolean {
|
||||
if (this.overlayRef) return false;
|
||||
|
||||
this.beforeEmojiCaretPosition = this.replyElement.nativeElement.selectionStart;
|
||||
|
||||
let topPosition = e.pageY;
|
||||
if (this.innerHeight - e.pageY < 360) {
|
||||
topPosition -= 360;
|
||||
}
|
||||
|
||||
let config = new OverlayConfig();
|
||||
config.positionStrategy = this.overlay.position()
|
||||
.global()
|
||||
.left(`${e.pageX - 283}px`)
|
||||
.top(`${topPosition}px`);
|
||||
config.hasBackdrop = true;
|
||||
|
||||
this.overlayRef = this.overlay.create(config);
|
||||
// this.overlayRef.backdropClick().subscribe(() => {
|
||||
// console.warn('wut?');
|
||||
// this.overlayRef.dispose();
|
||||
// });
|
||||
|
||||
let comp = new ComponentPortal(EmojiPickerComponent);
|
||||
const compRef: ComponentRef<EmojiPickerComponent> = this.overlayRef.attach(comp);
|
||||
this.emojiCloseSub = compRef.instance.closedEvent.subscribe(() => {
|
||||
this.closeEmojiPanel();
|
||||
});
|
||||
this.emojiSelectedSub = compRef.instance.emojiSelectedEvent.subscribe((emoji) => {
|
||||
if (emoji) {
|
||||
this.status = [this.status.slice(0, this.beforeEmojiCaretPosition), emoji, ' ', this.status.slice(this.beforeEmojiCaretPosition)].join('').replace(' ', ' ');
|
||||
this.beforeEmojiCaretPosition += emoji.length + 1;
|
||||
|
||||
this.closeEmojiPanel();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private closeEmojiPanel() {
|
||||
if (this.emojiCloseSub) this.emojiCloseSub.unsubscribe();
|
||||
if (this.emojiSelectedSub) this.emojiSelectedSub.unsubscribe();
|
||||
if (this.overlayRef) this.overlayRef.dispose();
|
||||
this.overlayRef = null;
|
||||
this.focus(this.beforeEmojiCaretPosition);
|
||||
}
|
||||
|
||||
closeEmoji(): boolean {
|
||||
this.overlayRef.dispose();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,8 @@
|
||||
<emoji-mart
|
||||
[showPreview]="false"
|
||||
[perLine]="7"
|
||||
[isNative]="true"
|
||||
[sheetSize]="16"
|
||||
[emojiTooltip]="true"
|
||||
(emojiSelect)="emojiSelected($event)"
|
||||
class="emojipicker" title="Pick your emoji…" emoji="point_up"></emoji-mart>
|
@ -0,0 +1,16 @@
|
||||
::ng-deep .emoji-mart {
|
||||
border-radius: 0 !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
::ng-deep .emoji-mart-emoji-native {
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
::ng-deep .emoji-mart-emoji-native span {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
left: -1px;
|
||||
font-size: 19px !important;
|
||||
cursor: pointer !important;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EmojiPickerComponent } from './emoji-picker.component';
|
||||
|
||||
xdescribe('EmojiPickerComponent', () => {
|
||||
let component: EmojiPickerComponent;
|
||||
let fixture: ComponentFixture<EmojiPickerComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ EmojiPickerComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EmojiPickerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,35 @@
|
||||
import { Component, OnInit, HostListener, ElementRef, Output, EventEmitter } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-emoji-picker',
|
||||
templateUrl: './emoji-picker.component.html',
|
||||
styleUrls: ['./emoji-picker.component.scss']
|
||||
})
|
||||
export class EmojiPickerComponent implements OnInit {
|
||||
private init = false;
|
||||
|
||||
@Output('closed') public closedEvent = new EventEmitter();
|
||||
@Output('emojiSelected') public emojiSelectedEvent = new EventEmitter<string>();
|
||||
|
||||
constructor(private eRef: ElementRef) { }
|
||||
|
||||
@HostListener('document:click', ['$event'])
|
||||
clickout(event) {
|
||||
if (!this.init) return;
|
||||
|
||||
if (!this.eRef.nativeElement.contains(event.target)) {
|
||||
this.closedEvent.emit(null);
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
setTimeout(() => {
|
||||
this.init = true;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
emojiSelected(select: any): boolean {
|
||||
this.emojiSelectedEvent.next(select.emoji.native);
|
||||
return false;
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<div *ngIf="m.attachment === null" class="media__loading" title="{{m.file.name}}">
|
||||
<app-waiting-animation class="waiting-icon"></app-waiting-animation>
|
||||
</div>
|
||||
<div *ngIf="m.attachment !== null && !m.audioType" class="media__loaded" title="{{m.file.name}}"
|
||||
<div *ngIf="m.attachment !== null && m.attachment.type !== 'audio'" class="media__loaded" title="{{m.file.name}}"
|
||||
(mouseleave)="updateMedia(m)">
|
||||
<div class="media__loaded--migrating" *ngIf="m.isMigrating">
|
||||
<app-waiting-animation class="waiting-icon"></app-waiting-animation>
|
||||
@ -14,15 +14,18 @@
|
||||
<input class="media__loaded--description" [(ngModel)]="m.description" autocomplete="off"
|
||||
placeholder="Describe for the visually impaired" />
|
||||
</div>
|
||||
<img *ngIf="!m.audioType" class="media__loaded--preview" src="{{m.attachment.preview_url}}" />
|
||||
|
||||
<img class="media__loaded--preview" src="{{m.attachment.preview_url}}" />
|
||||
</div>
|
||||
<div *ngIf="m.attachment !== null && m.audioType" class="audio">
|
||||
<div *ngIf="m.attachment !== null && m.attachment.type === 'audio'" class="audio">
|
||||
<button class="audio__button" title="remove" (click)="removeMedia(m)">
|
||||
<fa-icon [icon]="faTimes"></fa-icon>
|
||||
</button>
|
||||
|
||||
<audio *ngIf="m.audioType" controls class="audio__player">
|
||||
<div *ngIf="m.isMigrating">
|
||||
<app-waiting-animation class="waiting-icon"></app-waiting-animation>
|
||||
</div>
|
||||
|
||||
<audio *ngIf="m.audioType && !m.isMigrating" controls class="audio__player">
|
||||
<source src="{{ m.attachment.url }}" type="{{ m.audioType }}">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
|
@ -71,7 +71,7 @@ export interface Application {
|
||||
|
||||
export interface Attachment {
|
||||
id: string;
|
||||
type: 'image' | 'video' | 'gifv';
|
||||
type: 'image' | 'video' | 'gifv' | 'audio';
|
||||
url: string;
|
||||
remote_url: string;
|
||||
preview_url: string;
|
||||
|
@ -9,6 +9,7 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es5",
|
||||
"downlevelIteration": true,
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user