Sengi-Windows-MacOS-Linux/src/app/components/create-status/create-status.component.ts

1058 lines
38 KiB
TypeScript
Raw Normal View History

2019-07-28 01:03:26 +02:00
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ElementRef, ViewChild, ViewContainerRef, ComponentRef, HostListener } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
2020-04-24 06:08:22 +02:00
import { Overlay, OverlayConfig, FullscreenOverlayContainer, OverlayRef } from '@angular/cdk/overlay';
import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
import { Store } from '@ngxs/store';
import { Subscription, Observable } from 'rxjs';
2019-07-25 00:15:56 +02:00
import { UP_ARROW, DOWN_ARROW, ENTER, ESCAPE } from '@angular/cdk/keycodes';
import { faPaperclip, faGlobe, faGlobeAmericas, faLock, faLockOpen, faEnvelope, faPollH } from "@fortawesome/free-solid-svg-icons";
import { faClock, faWindowClose as faWindowCloseRegular } from "@fortawesome/free-regular-svg-icons";
2019-07-25 07:10:48 +02:00
import { ContextMenuService, ContextMenuComponent } from 'ngx-contextmenu';
import { VisibilityEnum, PollParameters } from '../../services/mastodon.service';
import { MastodonWrapperService } from '../../services/mastodon-wrapper.service';
2023-08-06 08:53:24 +02:00
import { Status, Attachment, Poll } from '../../services/models/mastodon.interfaces';
import { ToolsService, InstanceInfo, InstanceType } from '../../services/tools.service';
import { NotificationService } from '../../services/notification.service';
import { StatusWrapper } from '../../models/common.model';
2019-03-06 05:02:11 +01:00
import { AccountInfo } from '../../states/accounts.state';
import { InstancesInfoService } from '../../services/instances-info.service';
import { MediaService, MediaWrapper } from '../../services/media.service';
2019-07-25 00:09:50 +02:00
import { AutosuggestSelection, AutosuggestUserActionEnum } from './autosuggest/autosuggest.component';
2019-07-27 23:01:47 +02:00
import { EmojiPickerComponent } from './emoji-picker/emoji-picker.component';
2019-08-25 03:28:04 +02:00
import { PollEditorComponent } from './poll-editor/poll-editor.component';
2019-08-25 04:47:54 +02:00
import { StatusSchedulerComponent } from './status-scheduler/status-scheduler.component';
import { ScheduledStatusService } from '../../services/scheduled-status.service';
2020-04-24 06:08:22 +02:00
import { StatusesStateService } from '../../services/statuses-state.service';
import { SettingsService } from '../../services/settings.service';
2023-08-04 07:08:00 +02:00
import { LanguageService } from '../../services/language.service';
2023-08-04 09:26:24 +02:00
import { ILanguage } from '../../states/settings.state';
2023-08-05 02:23:40 +02:00
import { LeftPanelType, NavigationService } from '../../services/navigation.service';
@Component({
selector: 'app-create-status',
templateUrl: './create-status.component.html',
styleUrls: ['./create-status.component.scss']
})
export class CreateStatusComponent implements OnInit, OnDestroy {
faPaperclip = faPaperclip;
faGlobe = faGlobe;
faGlobeAmericas = faGlobeAmericas;
2019-07-25 07:10:48 +02:00
faLock = faLock;
faLockOpen = faLockOpen;
faEnvelope = faEnvelope;
faPollH = faPollH;
faClock = faClock;
2019-07-25 00:09:50 +02:00
autoSuggestUserActionsStream = new EventEmitter<AutosuggestUserActionEnum>();
private isRedrafting: boolean;
2019-07-25 00:09:50 +02:00
private _title: string;
2019-07-04 01:55:33 +02:00
set title(value: string) {
this._title = value;
this.countStatusChar(this.status);
}
get title(): string {
return this._title;
}
2019-03-07 01:02:29 +01:00
private _status: string = '';
2019-07-06 06:20:03 +02:00
@Input('status')
2019-03-11 05:31:56 +01:00
set status(value: string) {
if (this.isRedrafting) {
this.statusStateService.setStatusContent(value, null);
} else {
this.statusStateService.setStatusContent(value, this.statusReplyingToWrapper);
}
2019-07-28 03:24:57 +02:00
this.countStatusChar(value);
this.detectAutosuggestion(value);
this._status = value;
2019-07-27 04:21:16 +02:00
2023-08-12 07:33:07 +02:00
this.languageService.autoDetectLang(value);
2019-07-28 03:24:57 +02:00
setTimeout(() => {
this.autoGrow();
}, 0);
2019-03-07 01:02:29 +01:00
}
get status(): string {
return this._status;
}
2019-03-07 02:45:36 +01:00
2020-04-24 06:55:53 +02:00
private trim(s, mask) {
while (~mask.indexOf(s[0])) {
s = s.slice(1);
}
while (~mask.indexOf(s[s.length - 1])) {
s = s.slice(0, -1);
}
return s;
}
2022-11-19 18:16:31 +01:00
@Input('statusToEdit')
set statusToEdit(value: StatusWrapper) {
if (value) {
this.isEditing = true;
2022-12-11 01:19:02 +01:00
this.editingStatusId = value.status.id;
2022-11-19 18:16:31 +01:00
this.redraftedStatus = value;
2023-04-24 06:17:13 +02:00
this.mediaService.loadMedia(value.status.media_attachments);
2022-11-19 18:16:31 +01:00
}
}
2019-07-08 00:58:56 +02:00
@Input('redraftedStatus')
set redraftedStatus(value: StatusWrapper) {
if (value) {
this.isRedrafting = true;
2019-07-27 00:20:39 +02:00
this.statusLoaded = false;
2022-12-11 01:40:17 +01:00
if (value.status && value.status.media_attachments) {
for (const m of value.status.media_attachments) {
this.mediaService.addExistingMedia(new MediaWrapper(m.id, null, m));
}
}
2020-04-24 06:55:53 +02:00
const newLine = String.fromCharCode(13, 10);
let content = value.status.content;
2020-04-26 07:04:55 +02:00
content = this.tranformHtmlRepliesToReplies(content);
2020-04-24 06:55:53 +02:00
while (content.includes('<p>') || content.includes('</p>') || content.includes('<br>') || content.includes('<br/>') || content.includes('<br />')) {
content = content.replace('<p>', '').replace('</p>', newLine + newLine).replace('<br />', newLine).replace('<br/>', newLine).replace('<br>', newLine);
}
content = this.trim(content, newLine);
2020-04-26 07:04:55 +02:00
let parser = new DOMParser();
2020-04-24 06:55:53 +02:00
var dom = parser.parseFromString(content, 'text/html')
2019-07-09 06:20:05 +02:00
this.status = dom.body.textContent;
2019-07-25 00:09:50 +02:00
// this.statusStateService.setStatusContent(this.status, this.statusReplyingToWrapper);
2020-04-24 06:08:22 +02:00
2019-07-08 00:58:56 +02:00
this.setVisibilityFromStatus(value.status);
this.title = value.status.spoiler_text;
2019-07-27 00:20:39 +02:00
this.statusLoaded = true;
2019-07-08 00:58:56 +02:00
if (value.status.in_reply_to_id) {
this.isSending = true;
this.mastodonService.getStatus(value.provider, value.status.in_reply_to_id)
.then((status: Status) => {
2020-04-01 08:29:51 +02:00
let cwResult = this.toolsService.checkContentWarning(status);
this.statusReplyingToWrapper = new StatusWrapper(cwResult.status, value.provider, cwResult.applyCw, cwResult.hide);
2019-07-08 00:58:56 +02:00
})
.catch(err => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, value.provider);
2019-07-08 00:58:56 +02:00
})
.then(() => {
2019-07-27 04:21:16 +02:00
this.isSending = false;
2019-07-08 00:58:56 +02:00
});
}
2023-08-06 08:53:24 +02:00
if(value.status.poll){
this.pollIsActive = true;
this.oldPoll = value.status.poll;
// setTimeout(() => {
// if(this.pollEditor) this.pollEditor.loadPollParameters(value.status.poll);
// }, 250);
}
2019-07-08 00:58:56 +02:00
}
}
2023-08-06 08:53:24 +02:00
oldPoll: Poll;
private maxCharLength: number;
charCountLeft: number;
2019-03-07 02:45:36 +01:00
postCounts: number = 1;
2019-03-06 05:37:58 +01:00
isSending: boolean;
2019-04-07 21:03:17 +02:00
mentionTooFarAwayError: boolean;
2019-07-23 04:30:29 +02:00
autosuggestData: string = null;
instanceSupportsPoll = true;
instanceSupportsScheduling = true;
2022-11-19 18:16:31 +01:00
isEditing: boolean;
2022-12-11 01:19:02 +01:00
editingStatusId: string;
2023-08-04 09:20:48 +02:00
configuredLanguages: ILanguage[] = [];
selectedLanguage: ILanguage;
2019-07-27 00:20:39 +02:00
private statusLoaded: boolean;
2019-07-27 04:21:16 +02:00
private hasSuggestions: boolean;
2019-03-06 05:37:58 +01:00
@Input() statusReplyingToWrapper: StatusWrapper;
@Output() onClose = new EventEmitter();
@ViewChild('reply') replyElement: ElementRef;
2019-07-26 04:50:51 +02:00
@ViewChild('fileInput') fileInputElement: ElementRef;
@ViewChild('footer') footerElement: ElementRef;
2019-07-25 07:10:48 +02:00
@ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent;
2023-08-04 08:39:59 +02:00
@ViewChild('langContextMenu') public langContextMenu: ContextMenuComponent;
2019-08-25 03:28:04 +02:00
@ViewChild(PollEditorComponent) pollEditor: PollEditorComponent;
2019-08-25 04:47:54 +02:00
@ViewChild(StatusSchedulerComponent) statusScheduler: StatusSchedulerComponent;
2019-07-04 01:55:33 +02:00
private _isDirectMention: boolean;
@Input('isDirectMention')
set isDirectMention(value: boolean) {
2019-07-05 23:22:33 +02:00
if (value) {
this._isDirectMention = value;
this.initMention();
}
2019-07-04 01:55:33 +02:00
}
get isDirectMention(): boolean {
return this._isDirectMention;
}
private _replyingUserHandle: string;
@Input('replyingUserHandle')
set replyingUserHandle(value: string) {
2019-07-27 04:21:16 +02:00
if (value) {
2019-07-05 23:22:33 +02:00
this._replyingUserHandle = value;
this.initMention();
}
2019-07-04 01:55:33 +02:00
}
get replyingUserHandle(): string {
return this._replyingUserHandle;
}
private statusReplyingTo: Status;
selectedPrivacy = 'Public';
2020-06-09 01:29:16 +02:00
private selectedPrivacySetByRedraft = false;
private accounts$: Observable<AccountInfo[]>;
private accountSub: Subscription;
2023-08-04 07:08:00 +02:00
private langSub: Subscription;
2023-08-04 08:39:59 +02:00
private selectLangSub: Subscription;
2019-07-04 01:55:33 +02:00
private selectedAccount: AccountInfo;
constructor(
2023-08-05 02:23:40 +02:00
private readonly navigationService: NavigationService,
2023-08-04 07:08:00 +02:00
private readonly languageService: LanguageService,
private readonly settingsService: SettingsService,
2023-08-04 07:08:00 +02:00
private readonly statusStateService: StatusesStateService,
private readonly scheduledStatusService: ScheduledStatusService,
2019-07-25 07:10:48 +02:00
private readonly contextMenuService: ContextMenuService,
private readonly store: Store,
private readonly notificationService: NotificationService,
private readonly toolsService: ToolsService,
private readonly mastodonService: MastodonWrapperService,
2019-03-10 22:38:10 +01:00
private readonly instancesInfoService: InstancesInfoService,
2019-07-27 23:01:47 +02:00
private readonly mediaService: MediaService,
private readonly overlay: Overlay,
2022-12-11 00:31:54 +01:00
public viewContainerRef: ViewContainerRef,
private readonly statusesStateService: StatusesStateService) {
2020-04-24 06:08:22 +02:00
2019-03-11 05:31:56 +01:00
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
}
2023-08-04 08:39:59 +02:00
private initLanguages(){
2023-08-04 07:08:00 +02:00
this.configuredLanguages = this.languageService.getConfiguredLanguages();
this.selectedLanguage = this.languageService.getSelectedLanguage();
this.langSub = this.languageService.configuredLanguagesChanged.subscribe(l => {
this.configuredLanguages = l;
2023-08-04 08:39:59 +02:00
// if(this.configuredLanguages.length > 0
// && this.selectedLanguage
// && this.configuredLanguages.findIndex(x => x.iso639 === this.selectedLanguage.iso639)){
// this.languageService.setSelectedLanguage(this.configuredLanguages[0]);
// }
});
this.selectLangSub = this.languageService.selectedLanguageChanged.subscribe(l => {
this.selectedLanguage = l;
2023-08-04 07:08:00 +02:00
});
2023-08-04 08:39:59 +02:00
if(!this.selectedLanguage && this.configuredLanguages.length > 0){
this.languageService.setSelectedLanguage(this.configuredLanguages[0]);
}
}
setLanguage(lang: ILanguage): boolean {
if(lang){
this.languageService.setSelectedLanguage(lang);
}
return false;
}
ngOnInit() {
this.initLanguages();
2023-08-04 07:08:00 +02:00
if (!this.isRedrafting) {
this.status = this.statusStateService.getStatusContent(this.statusReplyingToWrapper);
}
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
this.accountChanged(accounts);
});
2019-07-04 01:55:33 +02:00
this.selectedAccount = this.toolsService.getSelectedAccounts()[0];
2019-07-05 23:22:33 +02:00
if (this.statusReplyingToWrapper) {
if (this.statusReplyingToWrapper.status.reblog) {
this.statusReplyingTo = this.statusReplyingToWrapper.status.reblog;
} else {
this.statusReplyingTo = this.statusReplyingToWrapper.status;
}
// let state = this.statusStateService.getStatusContent(this.statusReplyingToWrapper);
// if (state && state !== '') {
// this.status = state;
// } else {
if (!this.status || this.status === '') {
2021-07-15 03:00:52 +02:00
const uniqueMentions = this.getMentions(this.statusReplyingTo);
2020-04-24 06:08:22 +02:00
for (const mention of uniqueMentions) {
this.status += `@${mention} `;
}
}
2019-07-08 00:58:56 +02:00
this.setVisibilityFromStatus(this.statusReplyingTo);
2019-04-07 21:03:17 +02:00
this.title = this.statusReplyingTo.spoiler_text;
2019-07-04 01:55:33 +02:00
} else if (this.replyingUserHandle) {
this.initMention();
}
2019-07-27 00:20:39 +02:00
this.statusLoaded = true;
2019-07-04 01:55:33 +02:00
this.focus();
2019-07-28 01:03:26 +02:00
this.innerHeight = window.innerHeight;
2019-07-04 01:55:33 +02:00
}
ngOnDestroy() {
if (this.isRedrafting) {
this.statusStateService.resetStatusContent(null);
}
2019-07-04 01:55:33 +02:00
this.accountSub.unsubscribe();
2023-08-04 07:08:00 +02:00
this.langSub.unsubscribe();
2023-08-04 08:39:59 +02:00
this.selectLangSub.unsubscribe();
2019-07-04 01:55:33 +02:00
}
2023-08-05 02:23:40 +02:00
onNavigateToSettings(): boolean {
this.navigationService.openPanel(LeftPanelType.Settings);
return false;
}
onPaste(e: any) {
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
let blobs: File[] = [];
for (const item of items) {
if (item.type.indexOf('image') === 0) {
let blob = item.getAsFile();
blobs.push(blob);
}
}
this.handleFileInput(blobs);
2020-02-15 04:36:22 +01:00
}
changePrivacy(value: string): boolean {
this.selectedPrivacy = value;
return false;
}
2019-07-26 04:50:51 +02:00
addMedia(): boolean {
this.fileInputElement.nativeElement.click();
return false;
}
handleFileInput(files: File[]): boolean {
const acc = this.toolsService.getSelectedAccounts()[0];
this.mediaService.uploadMedia(acc, files);
return false;
}
2019-07-23 04:30:29 +02:00
private detectAutosuggestion(status: string) {
2019-07-27 00:20:39 +02:00
if (!this.statusLoaded) return;
2020-02-16 04:21:58 +01:00
if (!status.includes('@') && !status.includes('#')) {
2019-10-12 01:59:53 +02:00
this.autosuggestData = null;
this.hasSuggestions = false;
return;
}
2019-07-25 07:26:43 +02:00
2019-07-25 02:55:13 +02:00
const caretPosition = this.replyElement.nativeElement.selectionStart;
2019-10-12 01:59:53 +02:00
2019-10-05 05:05:36 +02:00
const lastChar = status.substr(caretPosition - 1, 1);
const lastCharIsSpace = lastChar === ' ';
2019-10-12 01:59:53 +02:00
2019-10-05 05:05:36 +02:00
const splitedStatus = status.split(/(\r\n|\n|\r)/);
let offset = 0;
2019-10-12 01:59:53 +02:00
let currentSection = '';
for (let x of splitedStatus) {
const sectionLength = x.length;
if (offset + sectionLength >= caretPosition) {
2019-10-05 05:05:36 +02:00
currentSection = x;
break;
} else {
offset += sectionLength;
}
};
const word = this.getWordByPos(currentSection, caretPosition - offset);
2022-12-11 04:26:06 +01:00
if (!lastCharIsSpace && word && word.length > 1 && (word.startsWith('@') || word.startsWith('#'))) {
2019-07-25 02:55:13 +02:00
this.autosuggestData = word;
return;
2019-07-23 04:30:29 +02:00
}
2019-10-12 01:59:53 +02:00
2019-07-23 04:30:29 +02:00
this.autosuggestData = null;
2019-10-05 05:05:36 +02:00
this.hasSuggestions = false;
2019-07-23 04:30:29 +02:00
}
2019-07-25 02:55:13 +02:00
private getWordByPos(str, pos) {
2019-10-05 05:05:36 +02:00
var preText = str.substring(0, pos);
if (preText.indexOf(" ") > 0) {
var words = preText.split(" ");
return words[words.length - 1]; //return last word
}
else {
return preText;
}
// // str = str.replace(/(\r\n|\n|\r)/gm, "");
// var left = str.substr(0, pos);
// var right = str.substr(pos);
2019-07-25 02:55:13 +02:00
2019-10-05 05:05:36 +02:00
// left = left.replace(/^.+ /g, "");
// right = right.replace(/ .+$/g, "");
2019-07-25 02:55:13 +02:00
2019-10-05 05:05:36 +02:00
// return left + right;
2019-07-25 02:55:13 +02:00
}
private focus(caretPos = null) {
setTimeout(() => {
this.replyElement.nativeElement.focus();
2019-07-25 02:55:13 +02:00
2019-07-25 07:51:11 +02:00
if (caretPos) {
2019-07-25 02:55:13 +02:00
this.replyElement.nativeElement.setSelectionRange(caretPos, caretPos);
} else {
this.replyElement.nativeElement.setSelectionRange(this.status.length, this.status.length);
}
}, 0);
}
2019-07-04 01:55:33 +02:00
private initMention() {
2019-07-27 00:20:39 +02:00
this.statusLoaded = false;
2019-07-04 01:55:33 +02:00
if (!this.selectedAccount) {
this.selectedAccount = this.toolsService.getSelectedAccounts()[0];
}
if (this.isDirectMention) {
this.setVisibility(VisibilityEnum.Direct);
} else {
this.getDefaultPrivacy();
}
2019-07-09 07:40:59 +02:00
this.status = `${this.replyingUserHandle} `;
2019-07-04 01:55:33 +02:00
this.countStatusChar(this.status);
2019-07-27 00:20:39 +02:00
this.statusLoaded = true;
2019-07-04 01:55:33 +02:00
this.focus();
}
private accountChanged(accounts: AccountInfo[]): void {
if (accounts && accounts.length > 0) {
2019-07-04 01:55:33 +02:00
this.selectedAccount = accounts.filter(x => x.isSelected)[0];
const settings = this.settingsService.getAccountSettings(this.selectedAccount);
if (settings.customStatusCharLengthEnabled) {
this.maxCharLength = settings.customStatusCharLength;
this.countStatusChar(this.status);
} else {
2019-07-04 01:55:33 +02:00
this.instancesInfoService.getMaxStatusChars(this.selectedAccount.instance)
.then((maxChars: number) => {
this.maxCharLength = maxChars;
this.countStatusChar(this.status);
})
.catch((err: HttpErrorResponse) => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, this.selectedAccount);
});
}
2019-07-04 01:55:33 +02:00
if (!this.statusReplyingToWrapper && !this.replyingUserHandle) {
this.getDefaultPrivacy();
2019-04-07 21:03:17 +02:00
}
this.toolsService.getInstanceInfo(this.selectedAccount)
.then((instance: InstanceInfo) => {
2019-10-12 01:59:53 +02:00
if (instance.type === InstanceType.Pixelfed) {
this.instanceSupportsPoll = false;
this.instanceSupportsScheduling = false;
this.pollIsActive = false;
this.scheduleIsActive = false;
} else {
this.instanceSupportsPoll = true;
this.instanceSupportsScheduling = true;
}
2019-10-12 01:59:53 +02:00
});
}
}
2019-07-04 01:55:33 +02:00
private getDefaultPrivacy() {
this.instancesInfoService.getDefaultPrivacy(this.selectedAccount)
.then((defaultPrivacy: VisibilityEnum) => {
this.setVisibility(defaultPrivacy);
})
.catch((err: HttpErrorResponse) => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, this.selectedAccount);
2019-07-04 01:55:33 +02:00
});
}
2019-07-08 00:58:56 +02:00
private setVisibilityFromStatus(status: Status) {
switch (status.visibility) {
case 'unlisted':
this.setVisibility(VisibilityEnum.Unlisted);
break;
case 'public':
this.setVisibility(VisibilityEnum.Public);
break;
case 'private':
this.setVisibility(VisibilityEnum.Private);
break;
case 'direct':
this.setVisibility(VisibilityEnum.Direct);
break;
}
2020-06-09 01:29:16 +02:00
this.selectedPrivacySetByRedraft = true;
2019-07-08 00:58:56 +02:00
}
2019-04-07 21:03:17 +02:00
private setVisibility(defaultPrivacy: VisibilityEnum) {
2022-12-11 01:19:02 +01:00
if (this.selectedPrivacySetByRedraft) return;
2020-06-09 01:29:16 +02:00
2019-04-07 21:03:17 +02:00
switch (defaultPrivacy) {
case VisibilityEnum.Public:
this.selectedPrivacy = 'Public';
break;
case VisibilityEnum.Unlisted:
this.selectedPrivacy = 'Unlisted';
break;
case VisibilityEnum.Private:
this.selectedPrivacy = 'Follows-only';
break;
case VisibilityEnum.Direct:
this.selectedPrivacy = 'DM';
break;
}
}
2019-03-11 05:31:56 +01:00
private countStatusChar(status: string) {
this.mentionTooFarAwayError = false;
2019-03-09 05:53:04 +01:00
const parseStatus = this.parseStatus(status);
const mentions = this.getMentionsFromStatus(status);
2019-04-07 21:03:17 +02:00
if (mentions.length > 0) {
let containAllMention = true;
mentions.forEach(m => {
2019-04-07 21:03:17 +02:00
if (!parseStatus[0].includes(m)) {
containAllMention = false;
}
});
2019-04-07 21:03:17 +02:00
if (!containAllMention) {
this.mentionTooFarAwayError = true;
this.charCountLeft = this.maxCharLength - status.length;
this.postCounts = 1;
return;
}
}
2019-03-09 05:53:04 +01:00
const currentStatus = parseStatus[parseStatus.length - 1];
2020-09-03 00:43:04 +02:00
const statusExtraChars = this.getMentionExtraChars(currentStatus);
const linksExtraChars = this.getLinksExtraChars(currentStatus);
2019-03-09 05:53:04 +01:00
2019-07-30 00:59:12 +02:00
const statusLength = [...currentStatus].length - statusExtraChars - linksExtraChars;
this.charCountLeft = this.maxCharLength - statusLength - this.getCwLength();
2019-03-09 05:53:04 +01:00
this.postCounts = parseStatus.length;
2019-03-07 02:45:36 +01:00
}
private getCwLength(): number {
let cwLength = 0;
if (this.title) {
2019-07-30 00:45:58 +02:00
cwLength = [...this.title].length;
}
return cwLength;
}
2021-07-15 03:00:52 +02:00
private getMentions(status: Status): string[] {
let acct = status.account.acct;
2022-12-11 01:19:02 +01:00
if (!acct.includes('@')) {
2021-07-15 03:00:52 +02:00
acct += `@${status.account.url.replace('https://', '').split('/')[0]}`
}
const mentions = [acct];
status.mentions.forEach(m => {
let mentionAcct = m.acct;
2022-12-11 01:19:02 +01:00
if (!mentionAcct.includes('@')) {
2021-07-15 03:00:52 +02:00
mentionAcct += `@${m.url.replace('https://', '').split('/')[0]}`;
}
mentions.push(mentionAcct);
});
let uniqueMentions = [];
for (let mention of mentions) {
if (!uniqueMentions.includes(mention)) {
uniqueMentions.push(mention);
}
}
2019-04-07 22:15:04 +02:00
const selectedUser = this.toolsService.getSelectedAccounts()[0];
2021-07-15 03:00:52 +02:00
uniqueMentions = uniqueMentions.filter(x => x.toLowerCase() !== `${selectedUser.username}@${selectedUser.instance}`.toLowerCase());
2019-07-27 04:21:16 +02:00
2021-07-15 03:00:52 +02:00
return uniqueMentions;
}
2019-03-11 05:31:56 +01:00
2019-03-09 05:30:50 +01:00
onCtrlEnter(): boolean {
this.onSubmit();
return false;
}
async onSubmit(): Promise<boolean> {
if (this.isSending || this.mentionTooFarAwayError) return false;
2019-03-06 05:37:58 +01:00
this.isSending = true;
let visibility: VisibilityEnum = VisibilityEnum.Unknown;
2019-04-07 21:03:17 +02:00
switch (this.selectedPrivacy) {
case 'Public':
visibility = VisibilityEnum.Public;
break;
case 'Unlisted':
visibility = VisibilityEnum.Unlisted;
break;
case 'Follows-only':
visibility = VisibilityEnum.Private;
break;
case 'DM':
visibility = VisibilityEnum.Direct;
break;
}
const acc = this.toolsService.getSelectedAccounts()[0];
const mediaAttachments = (await this.mediaService.retrieveUpToDateMedia(acc)).map(x => x.attachment);
let usableStatus: Promise<Status>;
if (this.statusReplyingToWrapper) {
usableStatus = this.toolsService.getStatusUsableByAccount(acc, this.statusReplyingToWrapper);
} else {
usableStatus = Promise.resolve(null);
}
2019-08-25 03:28:04 +02:00
let poll: PollParameters = null;
if (this.pollIsActive) {
poll = this.pollEditor.getPollParameters();
}
2019-08-25 04:47:54 +02:00
let scheduledTime = null;
2019-10-12 01:59:53 +02:00
if (this.scheduleIsActive) {
2019-08-25 04:47:54 +02:00
scheduledTime = this.statusScheduler.getScheduledDate();
2019-10-12 01:59:53 +02:00
if (!scheduledTime || scheduledTime === '') {
2019-08-25 04:47:54 +02:00
this.isSending = false;
return;
}
}
usableStatus
.then((status: Status) => {
2022-12-11 01:19:02 +01:00
return this.sendStatus(acc, this.status, visibility, this.title, status, mediaAttachments, poll, scheduledTime, this.editingStatusId);
})
.then((res: Status) => {
this.title = '';
this.status = '';
this.onClose.emit();
2019-10-12 01:59:53 +02:00
if (this.scheduleIsActive) {
this.scheduledStatusService.statusAdded(acc);
}
2020-04-24 06:08:22 +02:00
if (this.isRedrafting) {
this.statusStateService.resetStatusContent(null);
} else {
this.statusStateService.resetStatusContent(this.statusReplyingToWrapper);
}
})
.catch((err: HttpErrorResponse) => {
2019-09-07 23:52:07 +02:00
this.notificationService.notifyHttpError(err, acc);
2019-03-06 05:37:58 +01:00
})
.then(() => {
this.isSending = false;
});
return false;
}
2023-08-04 09:20:48 +02:00
private currentLang(): string {
if(this.selectedLanguage){
return this.selectedLanguage.iso639;
}
return null;
}
2022-12-11 01:19:02 +01:00
private sendStatus(account: AccountInfo, status: string, visibility: VisibilityEnum, title: string, previousStatus: Status, attachments: Attachment[], poll: PollParameters, scheduledAt: string, editingStatusId: string): Promise<Status> {
2019-03-09 06:14:44 +01:00
let parsedStatus = this.parseStatus(status);
let resultPromise = Promise.resolve(previousStatus);
2019-03-11 05:31:56 +01:00
for (let i = 0; i < parsedStatus.length; i++) {
2019-03-10 22:38:10 +01:00
let s = parsedStatus[i];
resultPromise = resultPromise
.then((pStatus: Status) => {
let inReplyToId = null;
if (pStatus) {
inReplyToId = pStatus.id;
}
if (i === 0) {
2022-12-11 01:19:02 +01:00
let postPromise: Promise<Status>;
if (this.isEditing) {
2023-08-04 09:20:48 +02:00
postPromise = this.mastodonService.editStatus(account, editingStatusId, s, visibility, title, inReplyToId, attachments, poll, scheduledAt, this.currentLang());
2022-12-11 01:19:02 +01:00
} else {
2023-08-04 09:20:48 +02:00
postPromise = this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, attachments.map(x => x.id), poll, scheduledAt, this.currentLang());
2022-12-11 01:19:02 +01:00
}
return postPromise
.then((status: Status) => {
this.mediaService.clearMedia();
return status;
});
} else {
2022-12-11 01:19:02 +01:00
if (this.isEditing) {
2023-08-04 09:20:48 +02:00
return this.mastodonService.editStatus(account, editingStatusId, s, visibility, title, inReplyToId, [], null, scheduledAt, this.currentLang());
2022-12-11 01:19:02 +01:00
} else {
2023-08-04 09:20:48 +02:00
return this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, [], null, scheduledAt, this.currentLang());
2022-12-11 01:19:02 +01:00
}
}
})
.then((status: Status) => {
if (this.statusReplyingToWrapper) {
2020-04-01 08:29:51 +02:00
let cwPolicy = this.toolsService.checkContentWarning(status);
this.notificationService.newStatusPosted(this.statusReplyingToWrapper.status.id, new StatusWrapper(cwPolicy.status, account, cwPolicy.applyCw, cwPolicy.hide));
}
2019-03-10 22:38:10 +01:00
return status;
2022-11-19 18:16:31 +01:00
})
.then((status: Status) => {
2022-12-11 01:19:02 +01:00
if (this.isEditing) {
2022-11-19 18:16:31 +01:00
let cwPolicy = this.toolsService.checkContentWarning(status);
2022-12-11 01:19:02 +01:00
let statusWrapper = new StatusWrapper(status, account, cwPolicy.applyCw, cwPolicy.hide);
2022-12-11 00:31:54 +01:00
2022-12-11 01:19:02 +01:00
this.statusesStateService.statusEditedStatusChanged(status.url, account.id, statusWrapper);
}
2022-11-19 18:16:31 +01:00
return status;
});
}
return resultPromise;
}
2019-03-09 05:30:50 +01:00
private parseStatus(status: string): string[] {
let mentionExtraChars = this.getMentionExtraChars(status);
2020-09-03 00:43:04 +02:00
let urlExtraChar = this.getLinksExtraChars(status);
2019-03-09 05:30:50 +01:00
let trucatedStatus = `${status}`;
let results = [];
let aggregateMention = '';
let mentions = this.getMentionsFromStatus(status);
mentions.forEach(x => {
2019-04-07 21:03:17 +02:00
aggregateMention += `${x} `;
});
2020-09-03 00:55:07 +02:00
let currentMaxCharLength = this.maxCharLength + mentionExtraChars + urlExtraChar - this.getCwLength();
let maxChars = currentMaxCharLength - 6;
while (trucatedStatus.length > currentMaxCharLength) {
2019-03-09 05:30:50 +01:00
const nextIndex = trucatedStatus.lastIndexOf(' ', maxChars);
2022-11-19 18:16:31 +01:00
2022-12-11 01:19:02 +01:00
if (nextIndex === -1) {
2020-07-01 01:58:38 +02:00
break;
}
2019-03-09 05:30:50 +01:00
results.push(trucatedStatus.substr(0, nextIndex) + ' (...)');
trucatedStatus = aggregateMention + trucatedStatus.substr(nextIndex + 1);
2020-09-03 00:55:07 +02:00
// Refresh max
let mentionExtraChars = this.getMentionExtraChars(trucatedStatus);
let urlExtraChar = this.getLinksExtraChars(trucatedStatus);
currentMaxCharLength = this.maxCharLength + mentionExtraChars + urlExtraChar - this.getCwLength();
maxChars = currentMaxCharLength - 6;
2019-03-09 05:30:50 +01:00
}
results.push(trucatedStatus);
return results;
}
2019-07-30 00:59:12 +02:00
private getLinksExtraChars(status: string): number {
let mentionExtraChars = 0;
2020-08-29 03:08:23 +02:00
let links = status.split(/\s+/).filter(x => x.startsWith('http://') || x.startsWith('https://'));
2019-07-30 00:59:12 +02:00
for (let link of links) {
if (link.length > 23) {
2019-07-30 00:59:12 +02:00
mentionExtraChars += link.length - 23;
}
}
return mentionExtraChars;
}
2019-04-07 21:03:17 +02:00
private getMentionExtraChars(status: string): number {
let mentionExtraChars = 0;
let mentions = this.getMentionsFromStatus(status);
for (const mention of mentions) {
if (mention.lastIndexOf('@') !== 0) {
const domain = mention.split('@')[2];
if (domain.length > 1) {
mentionExtraChars += (domain.length + 1);
}
}
}
return mentionExtraChars;
}
2019-04-07 21:03:17 +02:00
private getMentionsFromStatus(status: string): string[] {
return status.split(' ').filter(x => x.indexOf('@') === 0 && x.length > 1);
}
2019-07-23 05:52:48 +02:00
2019-07-25 00:09:50 +02:00
suggestionSelected(selection: AutosuggestSelection) {
2019-07-25 02:55:13 +02:00
if (this.status.includes(selection.pattern)) {
this.status = this.replacePatternWithAutosuggest(this.status, selection.pattern, selection.autosuggest);
2022-11-19 18:16:31 +01:00
let cleanStatus = this.status.replace(/\r?\n/g, ' ');
let newCaretPosition = cleanStatus.indexOf(`${selection.autosuggest}`) + selection.autosuggest.length;
if (newCaretPosition > cleanStatus.length) newCaretPosition = cleanStatus.length;
2019-07-25 02:55:13 +02:00
this.autosuggestData = null;
this.hasSuggestions = false;
2019-07-25 07:51:11 +02:00
2019-07-25 02:55:13 +02:00
if (document.activeElement === this.replyElement.nativeElement) {
setTimeout(() => {
this.replyElement.nativeElement.setSelectionRange(newCaretPosition, newCaretPosition);
}, 0);
} else {
this.focus(newCaretPosition);
}
2019-07-23 05:52:48 +02:00
}
}
2019-07-23 06:45:52 +02:00
private replacePatternWithAutosuggest(status: string, pattern: string, autosuggest: string): string {
2020-06-04 01:16:16 +02:00
status = status.replace(/ /g, ' ');
2020-06-04 01:16:16 +02:00
const newLine = String.fromCharCode(13, 10);
// let statusPerLines = status.split(newLine);
let statusPerLines = status.split(/\r?\n/);
let statusPerLinesPerWords: string[][] = [];
let regex = new RegExp(`^${pattern}$`, 'i');
2020-06-04 01:16:16 +02:00
statusPerLines.forEach(line => {
let words = line.split(' ');
2020-06-04 01:16:16 +02:00
words = words.map(word => {
return word.replace(regex, `${autosuggest}`);
});
statusPerLinesPerWords.push(words);
});
let result = '';
let nberLines = statusPerLinesPerWords.length;
let i = 0;
statusPerLinesPerWords.forEach(line => {
i++;
let wordCount = line.length;
let w = 0;
line.forEach(word => {
w++;
result += `${word}`;
2022-12-11 01:19:02 +01:00
if (w < wordCount || i === nberLines) {
2020-06-04 01:16:16 +02:00
result += ' ';
}
});
if (i < nberLines) {
result += newLine;
}
})
result = result.replace(' ', ' ');
let endRegex = new RegExp(`${autosuggest} $`, 'i');
2022-12-11 01:19:02 +01:00
if (!result.match(endRegex)) {
2020-06-04 01:16:16 +02:00
result = result.substring(0, result.length - 1);
}
return result;
}
suggestionsChanged(hasSuggestions: boolean) {
this.hasSuggestions = hasSuggestions;
}
2019-07-23 06:45:52 +02:00
handleKeyDown(event: KeyboardEvent): boolean {
2019-07-25 00:09:50 +02:00
if (this.hasSuggestions) {
2019-07-25 00:15:56 +02:00
let keycode = event.keyCode;
if (keycode === DOWN_ARROW || keycode === UP_ARROW || keycode === ENTER || keycode === ESCAPE) {
2019-07-23 06:45:52 +02:00
event.stopImmediatePropagation();
event.preventDefault();
2019-07-25 00:09:50 +02:00
event.stopPropagation();
2019-07-25 00:15:56 +02:00
switch (keycode) {
2019-07-25 00:09:50 +02:00
case DOWN_ARROW:
this.autoSuggestUserActionsStream.next(AutosuggestUserActionEnum.MoveDown);
break;
case UP_ARROW:
this.autoSuggestUserActionsStream.next(AutosuggestUserActionEnum.MoveUp);
break;
case ENTER:
this.autoSuggestUserActionsStream.next(AutosuggestUserActionEnum.Validate);
break;
2019-07-25 02:55:13 +02:00
case ESCAPE:
2019-07-25 00:15:56 +02:00
this.autosuggestData = null;
this.hasSuggestions = false;
break;
2019-07-25 00:09:50 +02:00
}
2019-07-25 02:55:13 +02:00
2019-07-23 06:45:52 +02:00
return false;
}
}
}
2019-07-25 00:21:16 +02:00
statusTextEditorLostFocus(): boolean {
2019-07-25 02:55:13 +02:00
setTimeout(() => {
this.autosuggestData = null;
this.hasSuggestions = false;
}, 250);
2019-07-25 00:21:16 +02:00
return false;
}
2019-07-25 07:10:48 +02:00
2019-07-27 04:21:16 +02:00
private autoGrow() {
2019-07-27 23:01:47 +02:00
let scrolling = (this.replyElement.nativeElement.scrollHeight);
2019-07-27 04:21:16 +02:00
2019-07-27 18:50:50 +02:00
if (scrolling > 110) {
2019-08-25 03:28:04 +02:00
const isVisible = this.checkVisible(this.footerElement.nativeElement);
2019-08-17 04:37:19 +02:00
//this.replyElement.nativeElement.style.height = `0px`;
2019-07-27 04:21:16 +02:00
this.replyElement.nativeElement.style.height = `${this.replyElement.nativeElement.scrollHeight}px`;
2019-08-17 04:06:06 +02:00
if (isVisible) {
setTimeout(() => {
try {
this.footerElement.nativeElement.scrollIntoViewIfNeeded({ behavior: 'instant', block: 'end', inline: 'start' });
} catch (err) {
this.footerElement.nativeElement.scrollIntoView({ behavior: 'instant', block: 'end', inline: 'start' });
}
2019-08-17 04:06:06 +02:00
}, 0);
}
2019-07-25 07:51:11 +02:00
}
}
2019-07-25 07:10:48 +02:00
private checkVisible(elm) {
var rect = elm.getBoundingClientRect();
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}
2019-07-25 07:10:48 +02:00
public onContextMenu($event: MouseEvent): void {
this.contextMenuService.show.next({
// Optional - if unspecified, all context menu components will open
contextMenu: this.contextMenu,
event: $event,
item: null
});
$event.preventDefault();
$event.stopPropagation();
}
2019-07-27 23:01:47 +02:00
2023-08-04 08:39:59 +02:00
public onLangContextMenu($event: MouseEvent): void {
this.contextMenuService.show.next({
// Optional - if unspecified, all context menu components will open
contextMenu: this.langContextMenu,
event: $event,
item: null
});
$event.preventDefault();
$event.stopPropagation();
}
2019-07-27 23:01:47 +02:00
//https://stackblitz.com/edit/overlay-demo
@ViewChild('emojiButton') emojiButtonElement: ElementRef;
overlayRef: OverlayRef;
2019-07-28 01:03:26 +02:00
public innerHeight: number;
@HostListener('window:resize', ['$event'])
onResize(event) {
this.innerHeight = window.innerHeight;
}
2019-07-28 02:50:37 +02:00
private emojiCloseSub: Subscription;
private emojiSelectedSub: Subscription;
private beforeEmojiCaretPosition: number;
2019-07-27 23:01:47 +02:00
openEmojiPicker(e: MouseEvent): boolean {
2019-07-28 02:50:37 +02:00
if (this.overlayRef) return false;
2019-07-27 23:01:47 +02:00
this.beforeEmojiCaretPosition = this.replyElement.nativeElement.selectionStart;
2019-07-28 01:03:26 +02:00
let topPosition = e.pageY;
2019-07-28 02:50:37 +02:00
if (this.innerHeight - e.pageY < 360) {
2019-07-28 01:03:26 +02:00
topPosition -= 360;
}
2019-07-27 23:01:47 +02:00
let config = new OverlayConfig();
config.positionStrategy = this.overlay.position()
.global()
2019-07-28 02:50:37 +02:00
.left(`${e.pageX - 283}px`)
2019-07-28 01:03:26 +02:00
.top(`${topPosition}px`);
2019-07-27 23:01:47 +02:00
config.hasBackdrop = true;
this.overlayRef = this.overlay.create(config);
2019-07-28 02:50:37 +02:00
// this.overlayRef.backdropClick().subscribe(() => {
// this.overlayRef.dispose();
// });
2019-07-28 01:03:26 +02:00
let comp = new ComponentPortal(EmojiPickerComponent);
2019-07-27 23:43:16 +02:00
const compRef: ComponentRef<EmojiPickerComponent> = this.overlayRef.attach(comp);
2019-07-28 02:50:37 +02:00
this.emojiCloseSub = compRef.instance.closedEvent.subscribe(() => {
this.closeEmojiPanel();
2019-07-28 01:09:49 +02:00
});
2019-07-28 02:50:37 +02:00
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();
2019-07-28 02:50:37 +02:00
}
2019-07-28 01:09:49 +02:00
});
2019-07-27 23:43:16 +02:00
2019-07-27 23:01:47 +02:00
return false;
}
private closeEmojiPanel() {
if (this.emojiCloseSub) this.emojiCloseSub.unsubscribe();
if (this.emojiSelectedSub) this.emojiSelectedSub.unsubscribe();
if (this.overlayRef) this.overlayRef.dispose();
2019-07-28 02:50:37 +02:00
this.overlayRef = null;
this.focus(this.beforeEmojiCaretPosition);
2019-07-28 02:50:37 +02:00
}
2019-07-27 23:01:47 +02:00
closeEmoji(): boolean {
this.overlayRef.dispose();
return false;
}
pollIsActive: boolean;
addPoll(): boolean {
this.pollIsActive = !this.pollIsActive;
return false;
}
scheduleIsActive: boolean;
schedule(): boolean {
this.scheduleIsActive = !this.scheduleIsActive;
return false;
}
2020-04-26 07:04:55 +02:00
private tranformHtmlRepliesToReplies(data: string): string {
const mastodonMentionRegex = /<span class="h-card"><a href="https:\/\/([a-zA-Z0-9.]{0,255})\/[a-zA-Z0-9_@/-]{0,255}" class="u-url mention">@<span>([a-zA-Z0-9_-]{0,255})<\/span><\/a><\/span>/gmi;
const pleromaMentionRegex = /<span class="h-card"><a data-user="[a-zA-Z0-9]{0,255}" class="u-url mention" href="https:\/\/([a-zA-Z0-9.]{0,255})\/[a-zA-Z0-9_@/-]{0,255}" rel="ugc">@<span>([a-zA-Z0-9_-]{0,255})<\/span><\/a><\/span>/gmi;
2020-04-26 07:04:55 +02:00
while (data.match(mastodonMentionRegex)) {
2020-04-26 07:04:55 +02:00
data = data.replace(mastodonMentionRegex, '@$2@$1');
}
while (data.match(pleromaMentionRegex)) {
2020-04-26 07:04:55 +02:00
data = data.replace(pleromaMentionRegex, '@$2@$1');
}
return data;
}
}