Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ Sengi is a multi-account Mastodon/Pleroma desktop client built with Angular 7, T

When editing colors, use css variables following the current themes implementation.
The themes colors are stored in the *.ts files in the `/src/app/themes/implementations` folder, they MUST be updated accordingly.
And don't forget to update the `/src/sass/_variables.scss` file when doing so.

You MUST update the `/src/sass/_variables.scss` file accordingly, when updating theme's variables.

## Commands

Expand Down
4 changes: 3 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ import { LabelsTutorialComponent } from './components/tutorial-enhanced/labels-t
import { ThankyouTutorialComponent } from './components/tutorial-enhanced/thankyou-tutorial/thankyou-tutorial.component';
import { StatusTranslateComponent } from './components/stream/status/status-translate/status-translate.component';
import { ThemeModule } from "./themes/theme.module";
import { QuoteComponent } from './components/stream/status/quote/quote.component';

const routes: Routes = [
{ path: "", component: StreamsMainDisplayComponent },
Expand Down Expand Up @@ -162,7 +163,8 @@ const routes: Routes = [
NotificationsTutorialComponent,
LabelsTutorialComponent,
ThankyouTutorialComponent,
StatusTranslateComponent
StatusTranslateComponent,
QuoteComponent
],
entryComponents: [
EmojiPickerComponent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,11 +290,7 @@ export class SettingsComponent implements OnInit, OnDestroy {

onThemeChange(themeId: number) {
const castedType = <ThemeTypeEnum>themeId;
console.warn(themeId);
console.warn(this.themeList);

const newTheme = this.themeList.find(x => x.theme_type == castedType);
console.warn(newTheme);

if(newTheme) this.themeService.setTheme(newTheme);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ $expand-color: $column-color;
color: $status-links-color;
}

& .invisible {
& .invisible, & .quote-inline {
display: none;
}

Expand Down
32 changes: 32 additions & 0 deletions src/app/components/stream/status/quote/quote.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div class="quote" *ngIf="quoteState == 'accepted' && displayStatus">
<a href class="quote__open-quote-btn" title="Open Status" (click)="textSelected()">
<fa-icon [icon]="faCircle"></fa-icon>
</a>
<a href class="quote__profile-link" title="{{displayStatus.account.acct}}"
(click)="openAccount(displayStatus.account)">
<img class="quote__avatar" src="{{ getAvatar(displayStatus.account) | ensureHttps }}" />
<span class="quote__name">
<span class="quote__name--displayname" innerHTML="{{displayStatus.account | accountEmoji}}"></span>
<span class="quote__name--username">{{displayStatus.account.acct}}</span>
</span>
</a>
<app-databinded-text #databindedtext class="quote__content" [text]="statusContent"
(accountSelected)="accountSelected($event)" (hashtagSelected)="hashtagSelected($event)"
(textSelected)="textSelected()"></app-databinded-text>

<app-poll class="quote__poll" *ngIf="displayStatus.poll" [poll]="displayStatus.poll"
[statusWrapper]="displayStatusWrapper"></app-poll>

<app-attachements *ngIf="displayStatus.media_attachments && displayStatus.media_attachments.length > 0" class="quote__attachments"
[attachments]="displayStatus.media_attachments">
</app-attachements>
<div *ngIf="displayStatus.quote" class="quote__sub-quote">
Quoted another post
</div>
</div>
<div class="quote quote__unaccepted" *ngIf="quoteState != 'accepted'">
{{ getReadableStatus(quoteState) }}
</div>
<div class="quote quote__unaccepted" *ngIf="error">
{{ error }}
</div>
97 changes: 97 additions & 0 deletions src/app/components/stream/status/quote/quote.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
@import "variables";
@import "commons";

.quote {
border: 1px solid $quote-border-color;
border-radius: 3px;
padding: 3px;
margin: 15px 2px 0 $avatar-column-space;
position: relative;

&__unaccepted {
color: $quote-unaccepted-color;
text-align: center;
font-size: 12px;
padding: 5px 10px;
}

&__open-quote-btn {
position: absolute;
top: 0px;
right: 1px;
color: $status-secondary-color;
text-decoration: none;
padding: 7px 7px;
font-size: 7px;
z-index: 10;

&:hover {
color: var(--status-secondary-color-lighten-20);
}
}

&__profile-link {
display: flex;
align-items: center;
text-decoration: none;
margin-bottom: 5px;
padding-right: 18px;

color: $status-secondary-color;
overflow: hidden;

&:hover {
color: var(--status-secondary-color-lighten-20);
}
}

&__avatar {
width: 30px;
height: 30px;
border-radius: 2px;
margin-right: 4px;
flex-shrink: 0;
}

&__name {
display: flex;
flex-direction: column;
align-items: flex-start;
overflow: hidden;
white-space: nowrap;

&--displayname {
line-height: 1;
color: $status-primary-color;
text-overflow: ellipsis;
overflow: hidden;
max-width: 100%;
}

&--username {
line-height: 1;
text-overflow: ellipsis;
overflow: hidden;
max-width: 100%;
}
}

&__attachments {
display: block;
margin-top: 5px;
}

&__sub-quote {
margin: 5px 0 0 0;
padding: 2px 0 2px 5px;
color: $quote-sub-quote-color;
background-color: $quote-sub-quote-background;
border-radius: 3px;
}

&__poll {
display: block;
margin-top: 5px;
}
}

25 changes: 25 additions & 0 deletions src/app/components/stream/status/quote/quote.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { QuoteComponent } from './quote.component';

xdescribe('QuoteComponent', () => {
let component: QuoteComponent;
let fixture: ComponentFixture<QuoteComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ QuoteComponent ]
})
.compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(QuoteComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
158 changes: 158 additions & 0 deletions src/app/components/stream/status/quote/quote.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Component, OnInit, Input, Output, EventEmitter, ApplicationRef } from '@angular/core';
import { faCircle } from '@fortawesome/free-solid-svg-icons';

import { Status, Account, Quote, ShallowQuote } from '../../../../services/models/mastodon.interfaces';
import { EmojiConverter, EmojiTypeEnum } from '../../../../tools/emoji.tools';
import { OpenThreadEvent, ToolsService } from '../../../../services/tools.service';
import { SettingsService } from '../../../../services/settings.service';
import { AccountInfo } from '../../../../states/accounts.state';
import { MastodonWrapperService } from '../../../../services/mastodon-wrapper.service';
import { StatusWrapper } from '../../../../models/common.model';

@Component({
selector: 'app-quote',
templateUrl: './quote.component.html',
styleUrls: ['./quote.component.scss']
})
export class QuoteComponent implements OnInit {
private emojiConverter = new EmojiConverter();
faCircle = faCircle;

displayStatus: Status;
displayStatusWrapper: StatusWrapper;
quoteState: 'pending' | 'accepted' | 'rejected' | 'revoked' | 'deleted' | 'unauthorized' | 'blocked_account' | 'blocked_domain' | 'muted_account';
error: string;

private quote: Quote;
private shallowQuote: ShallowQuote;

@Output() browseAccountEvent = new EventEmitter<string>();
@Output() browseHashtagEvent = new EventEmitter<string>();
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();

@Input() accountInfo: AccountInfo;

@Input('quote')
set setQuote(value: Quote | ShallowQuote) {

this.quoteState = value.state;
//this.quoteState = "revoked";

let quoteValue = <Quote>value;

if (quoteValue.quoted_status) { // Quote

this.quote = quoteValue;
this.displayStatus = quoteValue.quoted_status;

if (!this.displayStatus.account.display_name) {
this.displayStatus.account.display_name = this.displayStatus.account.username;
}

const statusContent = this.emojiConverter.applyEmojis(this.displayStatus.emojis, this.displayStatus.content, EmojiTypeEnum.medium);
this.statusContent = this.ensureMentionAreDisplayed(statusContent);

} else { // ShallowQuote
this.shallowQuote = <ShallowQuote>value;
this.mastodonService.getStatus(this.accountInfo, this.shallowQuote.quoted_status_id)
.then(status => {
this.displayStatus = status;
this.appRef.tick();
})
.catch(err => {
console.error(err);
this.error = "Error retrieving status.";
this.appRef.tick();
});
}

this.displayStatusWrapper = new StatusWrapper(this.displayStatus, this.accountInfo, false, false);
}

statusContent: string;
private freezeAvatarEnabled: boolean;

constructor(
private appRef: ApplicationRef,
private readonly settingsService: SettingsService,
private readonly toolsService: ToolsService,
private readonly mastodonService: MastodonWrapperService) {
this.freezeAvatarEnabled = this.settingsService.getSettings().enableFreezeAvatar;
}

ngOnInit() {
}

// TODO: refactorise this
private ensureMentionAreDisplayed(data: string): string {
const mentions = this.displayStatus.mentions;
if (!mentions || mentions.length === 0) {
return data;
}

let textMentions = '';
for (const m of mentions) {
if (!data.includes(m.url)) {
textMentions += `<span class="h-card">
<a class="u-url mention" data-user="${m.id}" href="${m.url}" rel="ugc">@
<span>${m.username}</span>
</a>
</span> `;
}
}
if (textMentions !== '') {
data = textMentions + data;
}
return data;
}

getReadableStatus(state: 'pending' | 'accepted' | 'rejected' | 'revoked' | 'deleted' | 'unauthorized' | 'blocked_account' | 'blocked_domain' | 'muted_account'): string {
switch (state) {
case "pending": return "Waiting for author authorization."
case "rejected": return "Author rejected quote, can't be displayed."
case "revoked": return "Author revoked quote, can't be displayed.";
case "deleted": return "Post deleted.";
case "unauthorized": return "Not authorized.";
case "blocked_account": return "Blocked account.";
case "blocked_domain": return "Blocked domain.";
case "muted_account": return "Muted account.";
}
return "";
}

accountSelected(accountName: string): void {
this.browseAccountEvent.next(accountName);
}

hashtagSelected(hashtag: string): void {
this.browseHashtagEvent.next(hashtag);
}

textSelected(): boolean {
let status = this.displayStatus;
const accountInfo = this.accountInfo;

if (status.reblog) {
status = status.reblog;
}

const openThread = new OpenThreadEvent(status, accountInfo);
this.browseThreadEvent.next(openThread);
return false;
}

getAvatar(acc: Account): string {
if (this.freezeAvatarEnabled) {
return acc.avatar_static;
} else {
return acc.avatar;
}
}

openAccount(account: Account): boolean {
let accountName = this.toolsService.getAccountFullHandle(account);
this.browseAccountEvent.emit(accountName);
return false;
}

}
9 changes: 8 additions & 1 deletion src/app/components/stream/status/status.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,17 @@
[attachments]="displayedStatus.media_attachments">
</app-attachements>

<app-quote *ngIf="!isContentWarned && displayedStatus.quote"
[quote]="displayedStatus.quote"
[accountInfo]="displayedStatusWrapper.provider"
(browseAccountEvent)="accountSelected($event)"
(browseHashtagEvent)="hashtagSelected($event)"
(browseThreadEvent)="browseThread($event)"></app-quote>

<app-action-bar #appActionBar [statusWrapper]="displayedStatusWrapper"
(browseThreadEvent)="browseThread($event)" (cwIsActiveEvent)="changeCw($event)"
(replyEvent)="openReply()"></app-action-bar>
</div>
</div>
<app-create-status *ngIf="replyingToStatus" [statusReplyingToWrapper]="displayedStatusWrapper"
(onClose)="closeReply()">
</app-create-status>
Expand Down
Loading