Post Release Shakeout (Hotfix Testing) (#1200)

* Fixed an issue where when falling back to folder parsing, sometimes the folder name wouldn't parse well, like "Foo 50" which parses as "Foo". Now the fallback will check if we have a solid series parsed from filename before we attempt to parse a folder.

* Ensure SortName is set during a scan loop even if locked and it's empty string.

* Added some null checks for metadata update

* Fixed a bug where Updating a series name with a name of an existing series wouldn't properly check for existing series.

* Tweaked the logic of OnDeck to consider LastChapterCreated from all chapters in a series, not just those with progress.

* Fixed a bug where the hamburger menu was still visible on login/registration page despite not functioning

* Tweaked the logic of OnDeck to consider LastChapterCreated from all chapters in a series, not just those with progress.

* Removed 2 unused packages from ui

* Fixed some bugs around determining what the current installed version is in Announcements

* Use AnyAsync for a query to improve performance

* Fixed up some fallback code

* Tests are finally fixed
This commit is contained in:
Joseph Milazzo 2022-04-05 09:39:03 -05:00 committed by GitHub
parent f10bc710ce
commit 428516b224
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 179 additions and 85 deletions

View File

@ -44,7 +44,7 @@ public class DefaultParserTests
[Theory] [Theory]
[InlineData("/manga/Btooom!/Vol.1/Chapter 1/1.cbz", "Btooom!~1~1")] [InlineData("/manga/Btooom!/Vol.1/Chapter 1/1.cbz", "Btooom!~1~1")]
[InlineData("/manga/Btooom!/Vol.1 Chapter 2/1.cbz", "Btooom!~1~2")] [InlineData("/manga/Btooom!/Vol.1 Chapter 2/1.cbz", "Btooom!~1~2")]
[InlineData("/manga/Monster #8/Ch. 001-016 [MangaPlus] [Digital] [amit34521]/Monster #8 Ch. 001 [MangaPlus] [Digital] [amit34521]/13.jpg", "Monster #8~0~1")] [InlineData("/manga/Monster/Ch. 001-016 [MangaPlus] [Digital] [amit34521]/Monster Ch. 001 [MangaPlus] [Digital] [amit34521]/13.jpg", "Monster~0~1")]
public void ParseFromFallbackFolders_ShouldParseSeriesVolumeAndChapter(string inputFile, string expectedParseInfo) public void ParseFromFallbackFolders_ShouldParseSeriesVolumeAndChapter(string inputFile, string expectedParseInfo)
{ {
const string rootDirectory = "/manga/"; const string rootDirectory = "/manga/";
@ -56,6 +56,27 @@ public class DefaultParserTests
Assert.Equal(tokens[2], actual.Chapters); Assert.Equal(tokens[2], actual.Chapters);
} }
[Theory]
[InlineData("/manga/Btooom!/Vol.1/Chapter 1/1.cbz", "Btooom!")]
[InlineData("/manga/Btooom!/Vol.1 Chapter 2/1.cbz", "Btooom!")]
[InlineData("/manga/Monster #8 (Digital)/Ch. 001-016 [MangaPlus] [Digital] [amit34521]/Monster #8 Ch. 001 [MangaPlus] [Digital] [amit34521]/13.jpg", "Monster")]
[InlineData("/manga/Monster (Digital)/Ch. 001-016 [MangaPlus] [Digital] [amit34521]/Monster Ch. 001 [MangaPlus] [Digital] [amit34521]/13.jpg", "Monster")]
[InlineData("/manga/Foo 50/Specials/Foo 50 SP01.cbz", "Foo 50")]
[InlineData("/manga/Foo 50 (kiraa)/Specials/Foo 50 SP01.cbz", "Foo 50")]
[InlineData("/manga/Btooom!/Specials/Just a special SP01.cbz", "Btooom!")]
public void ParseFromFallbackFolders_ShouldUseExistingSeriesName(string inputFile, string expectedParseInfo)
{
const string rootDirectory = "/manga/";
var fs = new MockFileSystem();
fs.AddDirectory(rootDirectory);
fs.AddFile(inputFile, new MockFileData(""));
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), fs);
var parser = new DefaultParser(ds);
var actual = parser.Parse(inputFile, rootDirectory);
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
Assert.Equal(expectedParseInfo, actual.Series);
}
#endregion #endregion
@ -243,6 +264,80 @@ public class DefaultParserTests
} }
} }
[Fact]
public void Parse_ParseInfo_Manga_WithSpecialsFolder()
{
const string rootPath = @"E:/Manga/";
var filesystem = new MockFileSystem();
filesystem.AddDirectory("E:/Manga");
filesystem.AddDirectory("E:/Foo 50");
filesystem.AddDirectory("E:/Foo 50/Specials");
filesystem.AddFile(@"E:/Manga/Foo 50/Foo 50 v1.cbz", new MockFileData(""));
filesystem.AddFile(@"E:/Manga/Foo 50/Specials/Foo 50 SP01.cbz", new MockFileData(""));
var ds = new DirectoryService(Substitute.For<ILogger<DirectoryService>>(), filesystem);
var parser = new DefaultParser(ds);
var filepath = @"E:/Manga/Foo 50/Foo 50 v1.cbz";
// There is a bad parse for series like "Foo 50", so we have parsed chapter as 50
var expected = new ParserInfo
{
Series = "Foo 50", Volumes = "1",
Chapters = "50", Filename = "Foo 50 v1.cbz", Format = MangaFormat.Archive,
FullFilePath = filepath
};
var actual = parser.Parse(filepath, rootPath);
Assert.NotNull(actual);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expected.Format, actual.Format);
_testOutputHelper.WriteLine("Format ✓");
Assert.Equal(expected.Series, actual.Series);
_testOutputHelper.WriteLine("Series ✓");
Assert.Equal(expected.Chapters, actual.Chapters);
_testOutputHelper.WriteLine("Chapters ✓");
Assert.Equal(expected.Volumes, actual.Volumes);
_testOutputHelper.WriteLine("Volumes ✓");
Assert.Equal(expected.Edition, actual.Edition);
_testOutputHelper.WriteLine("Edition ✓");
Assert.Equal(expected.Filename, actual.Filename);
_testOutputHelper.WriteLine("Filename ✓");
Assert.Equal(expected.FullFilePath, actual.FullFilePath);
_testOutputHelper.WriteLine("FullFilePath ✓");
Assert.Equal(expected.IsSpecial, actual.IsSpecial);
_testOutputHelper.WriteLine("IsSpecial ✓");
filepath = @"E:/Manga/Foo 50/Specials/Foo 50 SP01.cbz";
expected = new ParserInfo
{
Series = "Foo 50", Volumes = "0", IsSpecial = true,
Chapters = "50", Filename = "Foo 50 SP01.cbz", Format = MangaFormat.Archive,
FullFilePath = filepath
};
actual = parser.Parse(filepath, rootPath);
Assert.NotNull(actual);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expected.Format, actual.Format);
_testOutputHelper.WriteLine("Format ✓");
Assert.Equal(expected.Series, actual.Series);
_testOutputHelper.WriteLine("Series ✓");
Assert.Equal(expected.Chapters, actual.Chapters);
_testOutputHelper.WriteLine("Chapters ✓");
Assert.Equal(expected.Volumes, actual.Volumes);
_testOutputHelper.WriteLine("Volumes ✓");
Assert.Equal(expected.Edition, actual.Edition);
_testOutputHelper.WriteLine("Edition ✓");
Assert.Equal(expected.Filename, actual.Filename);
_testOutputHelper.WriteLine("Filename ✓");
Assert.Equal(expected.FullFilePath, actual.FullFilePath);
_testOutputHelper.WriteLine("FullFilePath ✓");
Assert.Equal(expected.IsSpecial, actual.IsSpecial);
_testOutputHelper.WriteLine("IsSpecial ✓");
}
[Fact] [Fact]
public void Parse_ParseInfo_Comic() public void Parse_ParseInfo_Comic()
{ {

View File

@ -145,13 +145,20 @@ namespace API.Controllers
if (series == null) return BadRequest("Series does not exist"); if (series == null) return BadRequest("Series does not exist");
if (series.Name != updateSeries.Name && await _unitOfWork.SeriesRepository.DoesSeriesNameExistInLibrary(updateSeries.Name, series.Format)) var seriesExists =
await _unitOfWork.SeriesRepository.DoesSeriesNameExistInLibrary(updateSeries.Name.Trim(), series.LibraryId,
series.Format);
if (series.Name != updateSeries.Name && seriesExists)
{ {
return BadRequest("A series already exists in this library with this name. Series Names must be unique to a library."); return BadRequest("A series already exists in this library with this name. Series Names must be unique to a library.");
} }
series.Name = updateSeries.Name.Trim(); series.Name = updateSeries.Name.Trim();
if (!string.IsNullOrEmpty(updateSeries.SortName.Trim()))
{
series.SortName = updateSeries.SortName.Trim(); series.SortName = updateSeries.SortName.Trim();
}
series.LocalizedName = updateSeries.LocalizedName.Trim(); series.LocalizedName = updateSeries.LocalizedName.Trim();
series.NameLocked = updateSeries.NameLocked; series.NameLocked = updateSeries.NameLocked;

View File

@ -47,7 +47,7 @@ public interface ISeriesRepository
void Update(Series series); void Update(Series series);
void Remove(Series series); void Remove(Series series);
void Remove(IEnumerable<Series> series); void Remove(IEnumerable<Series> series);
Task<bool> DoesSeriesNameExistInLibrary(string name, MangaFormat format); Task<bool> DoesSeriesNameExistInLibrary(string name, int libraryId, MangaFormat format);
/// <summary> /// <summary>
/// Adds user information like progress, ratings, etc /// Adds user information like progress, ratings, etc
/// </summary> /// </summary>
@ -135,17 +135,12 @@ public class SeriesRepository : ISeriesRepository
/// <param name="name">Name of series</param> /// <param name="name">Name of series</param>
/// <param name="format">Format of series</param> /// <param name="format">Format of series</param>
/// <returns></returns> /// <returns></returns>
public async Task<bool> DoesSeriesNameExistInLibrary(string name, MangaFormat format) public async Task<bool> DoesSeriesNameExistInLibrary(string name, int libraryId, MangaFormat format)
{ {
var libraries = _context.Series
.AsNoTracking()
.Where(x => x.Name.Equals(name) && x.Format == format)
.Select(s => s.LibraryId);
return await _context.Series return await _context.Series
.AsNoTracking() .AsNoTracking()
.Where(s => libraries.Contains(s.LibraryId) && s.Name.Equals(name) && s.Format == format) .Where(s => s.LibraryId == libraryId && s.Name.Equals(name) && s.Format == format)
.CountAsync() > 1; .AnyAsync();
} }
@ -624,13 +619,13 @@ public class SeriesRepository : ISeriesRepository
LastReadingProgress = _context.AppUserProgresses LastReadingProgress = _context.AppUserProgresses
.Where(p => p.Id == progress.Id && p.AppUserId == userId) .Where(p => p.Id == progress.Id && p.AppUserId == userId)
.Max(p => p.LastModified), .Max(p => p.LastModified),
// BUG: This is only taking into account chapters that have progress on them, not all chapters in said series // This is only taking into account chapters that have progress on them, not all chapters in said series
LastChapterCreated = _context.Chapter.Where(c => progress.ChapterId == c.Id).Max(c => c.Created), //LastChapterCreated = _context.Chapter.Where(c => progress.ChapterId == c.Id).Max(c => c.Created),
//LastChapterCreated = _context.Chapter.Where(c => allChapters.Contains(c.Id)).Max(c => c.Created) LastChapterCreated = s.Volumes.SelectMany(v => v.Chapters).Max(c => c.Created)
}); });
if (cutoffOnDate) if (cutoffOnDate)
{ {
query = query.Where(d => d.LastReadingProgress >= cutoffProgressPoint); query = query.Where(d => d.LastReadingProgress >= cutoffProgressPoint || d.LastChapterCreated >= cutoffProgressPoint);
} }
// I think I need another Join statement. The problem is the chapters are still limited to progress // I think I need another Join statement. The problem is the chapters are still limited to progress

View File

@ -11,7 +11,7 @@ namespace API.Entities.Metadata
{ {
public int Id { get; set; } public int Id { get; set; }
public string Summary { get; set; } public string Summary { get; set; } = string.Empty;
public ICollection<CollectionTag> CollectionTags { get; set; } public ICollection<CollectionTag> CollectionTags { get; set; }

View File

@ -142,19 +142,23 @@ public class DefaultParser
} }
} }
// Generally users group in series folders. Let's try to parse series from the top folder
if (!folder.Equals(ret.Series) && i == fallbackFolders.Count - 1)
{
var series = Parser.ParseSeries(folder); var series = Parser.ParseSeries(folder);
if ((string.IsNullOrEmpty(series) && i == fallbackFolders.Count - 1)) if (string.IsNullOrEmpty(series))
{ {
ret.Series = Parser.CleanTitle(folder, type is LibraryType.Comic); ret.Series = Parser.CleanTitle(folder, type is LibraryType.Comic);
break; break;
} }
if (!string.IsNullOrEmpty(series)) if (!string.IsNullOrEmpty(series) && (string.IsNullOrEmpty(ret.Series) || !folder.Contains(ret.Series)))
{ {
ret.Series = series; ret.Series = series;
break; break;
} }
} }
} }
}
} }

View File

@ -67,6 +67,17 @@ public class SeriesService : ISeriesService
series.Metadata.PublicationStatusLocked = true; series.Metadata.PublicationStatusLocked = true;
} }
// This shouldn't be needed post v0.5.3 release
if (string.IsNullOrEmpty(series.Metadata.Summary))
{
series.Metadata.Summary = string.Empty;
}
if (string.IsNullOrEmpty(updateSeriesMetadataDto.SeriesMetadata.Summary))
{
updateSeriesMetadataDto.SeriesMetadata.Summary = string.Empty;
}
if (series.Metadata.Summary != updateSeriesMetadataDto.SeriesMetadata.Summary.Trim()) if (series.Metadata.Summary != updateSeriesMetadataDto.SeriesMetadata.Summary.Trim())
{ {
series.Metadata.Summary = updateSeriesMetadataDto.SeriesMetadata?.Summary.Trim(); series.Metadata.Summary = updateSeriesMetadataDto.SeriesMetadata?.Summary.Trim();

View File

@ -523,13 +523,17 @@ public class ScannerService : IScannerService
series.Format = parsedInfos[0].Format; series.Format = parsedInfos[0].Format;
} }
series.OriginalName ??= parsedInfos[0].Series; series.OriginalName ??= parsedInfos[0].Series;
if (string.IsNullOrEmpty(series.SortName))
{
series.SortName = series.Name;
}
if (!series.SortNameLocked) if (!series.SortNameLocked)
{ {
series.SortName = series.Name;
if (!string.IsNullOrEmpty(parsedInfos[0].SeriesSort)) if (!string.IsNullOrEmpty(parsedInfos[0].SeriesSort))
{ {
series.SortName = parsedInfos[0].SeriesSort; series.SortName = parsedInfos[0].SeriesSort;
} }
series.SortName = series.Name;
} }
await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, series.Name)); await _eventHub.SendMessageAsync(MessageFactory.NotificationProgress, MessageFactory.LibraryScanProgressEvent(library.Name, ProgressEventType.Ended, series.Name));

View File

@ -92,12 +92,7 @@ public class VersionUpdaterService : IVersionUpdaterService
{ {
if (update == null || string.IsNullOrEmpty(update.Tag_Name)) return null; if (update == null || string.IsNullOrEmpty(update.Tag_Name)) return null;
var updateVersion = new Version(update.Tag_Name.Replace("v", string.Empty)); var updateVersion = new Version(update.Tag_Name.Replace("v", string.Empty));
var currentVersion = BuildInfo.Version.ToString(); var currentVersion = BuildInfo.Version.ToString(4);
if (updateVersion.Revision == -1)
{
currentVersion = currentVersion.Substring(0, currentVersion.LastIndexOf(".", StringComparison.Ordinal));
}
return new UpdateNotificationDto() return new UpdateNotificationDto()
{ {

View File

@ -2418,22 +2418,6 @@
"integrity": "sha512-wooUZiV92QyoeFxkhqIwH/cfiAAAn+l8fEEuaaEIfJtpjpbShvvlboEVsqb28soeGiFJfLcmsZM3mUFgsG4QBQ==", "integrity": "sha512-wooUZiV92QyoeFxkhqIwH/cfiAAAn+l8fEEuaaEIfJtpjpbShvvlboEVsqb28soeGiFJfLcmsZM3mUFgsG4QBQ==",
"dev": true "dev": true
}, },
"@ngx-lite/nav-drawer": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/@ngx-lite/nav-drawer/-/nav-drawer-0.4.7.tgz",
"integrity": "sha512-OqXJhzE88RR5Vtgr0tcuvkKVkzsKZjeXxhjpWOJ9UiC2iCQPDL2rtvag5K/vPKN362Jx0htvr3cmFDjHg/kjdA==",
"requires": {
"tslib": "^2.0.0"
}
},
"@ngx-lite/util": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/@ngx-lite/util/-/util-0.0.1.tgz",
"integrity": "sha512-j7pBcF+5OEHExEUBNdlQT5x4sVvHIPwZeMvhlO1TAcAAz9frDsvYgJ1c3eXJYJKJq57o1rH1RESKSJ9YRNpAiw==",
"requires": {
"tslib": "^2.1.0"
}
},
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",

View File

@ -29,8 +29,6 @@
"@fortawesome/fontawesome-free": "^6.0.0", "@fortawesome/fontawesome-free": "^6.0.0",
"@microsoft/signalr": "^6.0.2", "@microsoft/signalr": "^6.0.2",
"@ng-bootstrap/ng-bootstrap": "^12.0.0", "@ng-bootstrap/ng-bootstrap": "^12.0.0",
"@ngx-lite/nav-drawer": "^0.4.7",
"@ngx-lite/util": "0.0.1",
"@popperjs/core": "^2.11.2", "@popperjs/core": "^2.11.2",
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"bootstrap": "^5.1.2", "bootstrap": "^5.1.2",

View File

@ -116,6 +116,11 @@ export class ErrorInterceptor implements HttpInterceptor {
} }
private handleAuthError(error: any) { private handleAuthError(error: any) {
// Special hack for register url, to not care about auth
if (location.href.includes('/registration/confirm-email?token=')) {
return;
}
// NOTE: Signin has error.error or error.statusText available. // NOTE: Signin has error.error or error.statusText available.
// if statement is due to http/2 spec issue: https://github.com/angular/angular/issues/23334 // if statement is due to http/2 spec issue: https://github.com/angular/angular/issues/23334
this.accountService.logout(); this.accountService.logout();

View File

@ -1,19 +1,20 @@
<div class="changelog"> <div class="changelog">
<p class="pb-2">If you do not see an <span class="badge bg-secondary">Installed</span> tag, you are on a nightly release. Only major versions will show as available.</p>
<ng-container *ngFor="let update of updates; let indx = index;"> <ng-container *ngFor="let update of updates; let indx = index;">
<div class="card w-100 mb-2" style="width: 18rem;"> <div class="card w-100 mb-2" style="width: 18rem;">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">{{update.updateTitle}}&nbsp; <h4 class="card-title">{{update.updateTitle}}&nbsp;
<span class="badge bg-secondary" *ngIf="update.updateVersion === installedVersion">Installed</span> <span class="badge bg-secondary" *ngIf="update.updateVersion === update.currentVersion">Installed</span>
<span class="badge bg-secondary" *ngIf="update.updateVersion > installedVersion">Available</span> <span class="badge bg-secondary" *ngIf="update.updateVersion > update.currentVersion">Available</span>
</h4> </h4>
<h6 class="card-subtitle mb-2 text-muted">Published: {{update.publishDate | date: 'short'}}</h6> <h6 class="card-subtitle mb-1 mt-1 text-muted">Published: {{update.publishDate | date: 'short'}}</h6>
<pre class="card-text update-body"> <pre class="card-text update-body">
<app-read-more [text]="update.updateBody" [maxLength]="500"></app-read-more> <app-read-more [text]="update.updateBody" [maxLength]="500"></app-read-more>
</pre> </pre>
<a *ngIf="!update.isDocker && update.updateVersion === installedVersion" href="{{update.updateUrl}}" class="btn disabled btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank">Installed</a> <a *ngIf="!update.isDocker && update.updateVersion === update.currentVersion" href="{{update.updateUrl}}" class="btn disabled btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank">Installed</a>
<a *ngIf="!update.isDocker && update.updateVersion !== installedVersion" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank">Download</a> <a *ngIf="!update.isDocker && update.updateVersion !== update.currentVersion" href="{{update.updateUrl}}" class="btn btn-{{indx === 0 ? 'primary' : 'secondary'}} float-end" target="_blank">Download</a>
</div> </div>
</div> </div>
</ng-container> </ng-container>

View File

@ -11,23 +11,14 @@ export class ChangelogComponent implements OnInit {
updates: Array<UpdateVersionEvent> = []; updates: Array<UpdateVersionEvent> = [];
isLoading: boolean = true; isLoading: boolean = true;
installedVersion: string = '';
constructor(private serverService: ServerService) { } constructor(private serverService: ServerService) { }
ngOnInit(): void { ngOnInit(): void {
this.serverService.getServerInfo().subscribe(info => {
this.installedVersion = info.kavitaVersion;
this.serverService.getChangelog().subscribe(updates => { this.serverService.getChangelog().subscribe(updates => {
this.updates = updates; this.updates = updates;
this.isLoading = false; this.isLoading = false;
if (this.updates.filter(u => u.updateVersion === this.installedVersion).length === 0) {
// User is on a nightly version. Tell them the last stable is installed
this.installedVersion = this.updates[0].updateVersion;
}
});
}); });

View File

@ -1,7 +1,7 @@
<nav class="navbar navbar-expand-md navbar-dark fixed-top" *ngIf="navService?.navbarVisible$ | async"> <nav class="navbar navbar-expand-md navbar-dark fixed-top" *ngIf="navService?.navbarVisible$ | async">
<div class="container-fluid"> <div class="container-fluid">
<a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a> <a class="visually-hidden-focusable focus-visible" href="javascript:void(0);" (click)="moveFocus()">Skip to main content</a>
<a class="side-nav-toggle" (click)="hideSideNav()"><i class="fas fa-bars"></i></a> <a class="side-nav-toggle" *ngIf="navService?.sideNavVisibility$ | async" (click)="hideSideNav()"><i class="fas fa-bars"></i></a>
<a class="navbar-brand dark-exempt" routerLink="/library" routerLinkActive="active"><img class="logo" src="../../assets/images/logo.png" alt="kavita icon" aria-hidden="true"/><span class="d-none d-md-inline"> Kavita</span></a> <a class="navbar-brand dark-exempt" routerLink="/library" routerLinkActive="active"><img class="logo" src="../../assets/images/logo.png" alt="kavita icon" aria-hidden="true"/><span class="d-none d-md-inline"> Kavita</span></a>
<ul class="navbar-nav col me-auto"> <ul class="navbar-nav col me-auto">

View File

@ -1,4 +1,6 @@
<div> <div>
<span [innerHTML]="currentText | safeHtml"></span> <span [innerHTML]="currentText | safeHtml"></span>
<a [class.hidden]="hideToggle" *ngIf="text && text.length > maxLength" class="read-more-link" (click)="toggleView()">&nbsp;<i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}}"></i>&nbsp;Read {{isCollapsed ? 'More' : 'Less'}}</a> <a [class.hidden]="hideToggle" *ngIf="text && text.length > maxLength" class="read-more-link" (click)="toggleView()">
&nbsp;<i aria-hidden="true" class="fa fa-caret-{{isCollapsed ? 'down' : 'up'}}"></i>&nbsp;Read {{isCollapsed ? 'More' : 'Less'}}
</a>
</div> </div>

View File

@ -39,6 +39,7 @@ export class SideNavComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.accountService.currentUser$.pipe(take(1)).subscribe(user => { this.accountService.currentUser$.pipe(take(1)).subscribe(user => {
this.user = user; this.user = user;
if (this.user) { if (this.user) {
this.isAdmin = this.accountService.hasAdminRole(this.user); this.isAdmin = this.accountService.hasAdminRole(this.user);
} }
@ -54,8 +55,6 @@ export class SideNavComponent implements OnInit, OnDestroy {
this.libraries = libraries; this.libraries = libraries;
}); });
}); });
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@ -37,7 +37,10 @@ export class UserLoginComponent implements OnInit {
isLoaded: boolean = false; isLoaded: boolean = false;
constructor(private accountService: AccountService, private router: Router, private memberService: MemberService, constructor(private accountService: AccountService, private router: Router, private memberService: MemberService,
private toastr: ToastrService, private navService: NavService, private settingsService: SettingsService, private modalService: NgbModal) { } private toastr: ToastrService, private navService: NavService, private settingsService: SettingsService, private modalService: NgbModal) {
this.navService.showNavBar();
this.navService.hideSideNav();
}
ngOnInit(): void { ngOnInit(): void {
this.navService.showNavBar(); this.navService.showNavBar();