diff --git a/package.json b/package.json index 62c6902f..b72f4831 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bookmarks.dev", - "version": "8.5.0", + "version": "8.6.0", "license": "MIT", "scripts": { "ng": "ng", diff --git a/src/app/core/model/bookmark.ts b/src/app/core/model/bookmark.ts index 82e47402..d1d227d4 100644 --- a/src/app/core/model/bookmark.ts +++ b/src/app/core/model/bookmark.ts @@ -17,4 +17,5 @@ export interface Bookmark { starredBy?: string[]; likes?: number; youtubeVideoId?: string; + stackoverflowQuestionId?: string; } diff --git a/src/app/core/model/webpage-data.ts b/src/app/core/model/webpage-data.ts index aea2082b..bf865af9 100644 --- a/src/app/core/model/webpage-data.ts +++ b/src/app/core/model/webpage-data.ts @@ -1,5 +1,6 @@ export interface WebpageData { title: string; metaDescription: string; + tags: string[]; publishedOn?: Date; } diff --git a/src/app/personal/create/create-personal-bookmark.component.ts b/src/app/personal/create/create-personal-bookmark.component.ts index c524040a..ae495881 100644 --- a/src/app/personal/create/create-personal-bookmark.component.ts +++ b/src/app/personal/create/create-personal-bookmark.component.ts @@ -1,27 +1,28 @@ -import { debounceTime, distinctUntilChanged, map, startWith } from 'rxjs/operators'; -import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; -import { Bookmark } from '../../core/model/bookmark'; -import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { MarkdownService } from '../markdown.service'; -import { KeycloakService } from 'keycloak-angular'; -import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes'; -import { MatAutocompleteSelectedEvent, MatChipInputEvent } from '@angular/material'; -import { Observable, throwError as observableThrowError } from 'rxjs'; -import { languages } from '../../shared/language-options'; -import { tagsValidator } from '../../shared/tags-validation.directive'; -import { PublicBookmarksStore } from '../../public/bookmarks/store/public-bookmarks-store.service'; -import { PublicBookmarksService } from '../../public/bookmarks/public-bookmarks.service'; -import { descriptionSizeValidator } from '../../shared/description-size-validation.directive'; -import { RateBookmarkRequest, RatingActionType } from '../../core/model/rate-bookmark.request'; -import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; -import { PersonalBookmarksService } from '../../core/personal-bookmarks.service'; -import { UserDataStore } from '../../core/user/userdata.store'; -import { Logger } from '../../core/logger.service'; -import { Router } from '@angular/router'; -import { ErrorService } from '../../core/error/error.service'; -import { UserDataService } from '../../core/user-data.service'; -import { UserInfoStore } from '../../core/user/user-info.store'; -import { SuggestedTagsStore } from '../../core/user/suggested-tags.store'; +import {debounceTime, distinctUntilChanged, map, startWith} from 'rxjs/operators'; +import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; +import {Bookmark} from '../../core/model/bookmark'; +import {FormArray, FormBuilder, FormControl, FormGroup, Validators} from '@angular/forms'; +import {MarkdownService} from '../markdown.service'; +import {KeycloakService} from 'keycloak-angular'; +import {COMMA, ENTER, SPACE} from '@angular/cdk/keycodes'; +import {MatAutocompleteSelectedEvent, MatChipInputEvent} from '@angular/material'; +import {Observable, throwError as observableThrowError} from 'rxjs'; +import {languages} from '../../shared/language-options'; +import {tagsValidator} from '../../shared/tags-validation.directive'; +import {PublicBookmarksStore} from '../../public/bookmarks/store/public-bookmarks-store.service'; +import {PublicBookmarksService} from '../../public/bookmarks/public-bookmarks.service'; +import {descriptionSizeValidator} from '../../shared/description-size-validation.directive'; +import {RateBookmarkRequest, RatingActionType} from '../../core/model/rate-bookmark.request'; +import {HttpErrorResponse, HttpResponse} from '@angular/common/http'; +import {PersonalBookmarksService} from '../../core/personal-bookmarks.service'; +import {UserDataStore} from '../../core/user/userdata.store'; +import {Logger} from '../../core/logger.service'; +import {Router} from '@angular/router'; +import {ErrorService} from '../../core/error/error.service'; +import {UserDataService} from '../../core/user-data.service'; +import {UserInfoStore} from '../../core/user/user-info.store'; +import {SuggestedTagsStore} from '../../core/user/suggested-tags.store'; +import {WebpageData} from '../../core/model/webpage-data'; @Component({ selector: 'app-new-personal-bookmark-form', @@ -70,20 +71,20 @@ export class CreatePersonalBookmarkComponent implements OnInit { private router: Router, private errorService: ErrorService ) { - this.userInfoStore.getUserInfo$().subscribe(userInfo => { - this.userId = userInfo.sub; - - this.suggestedTagsStore.getSuggestedTags$(this.userId).subscribe(tags => { - this.autocompleteTags = tags.sort(); - - this.filteredTags = this.tagsControl.valueChanges.pipe( - startWith(null), - map((tag: string | null) => { - return tag ? this.filter(tag) : this.autocompleteTags.slice(); - }) - ); - }); + this.userInfoStore.getUserInfo$().subscribe(userInfo => { + this.userId = userInfo.sub; + + this.suggestedTagsStore.getSuggestedTags$(this.userId).subscribe(tags => { + this.autocompleteTags = tags.sort(); + + this.filteredTags = this.tagsControl.valueChanges.pipe( + startWith(null), + map((tag: string | null) => { + return tag ? this.filter(tag) : this.autocompleteTags.slice(); + }) + ); }); + }); } ngOnInit(): void { @@ -100,7 +101,8 @@ export class CreatePersonalBookmarkComponent implements OnInit { description: ['', descriptionSizeValidator], shared: false, language: 'en', - youtubeVideoId: null + youtubeVideoId: null, + stackoverflowQuestionId: null, }); this.bookmarkForm.get('location').valueChanges.pipe( @@ -108,12 +110,12 @@ export class CreatePersonalBookmarkComponent implements OnInit { distinctUntilChanged(), ) .subscribe(location => { this.personalBookmarksService.getPersonalBookmarkByLocation(this.userId, location).subscribe(httpResponse => { - if (httpResponse.status === 200) { - this.personalBookmarkPresent = true; - } else { - this.getScrapeData(location); - } - }, + if (httpResponse.status === 200) { + this.personalBookmarkPresent = true; + } else { + this.getScrapeData(location); + } + }, (errorResponse: HttpErrorResponse) => { if (errorResponse.status === 404) { this.getScrapeData(location); @@ -128,26 +130,68 @@ export class CreatePersonalBookmarkComponent implements OnInit { const youtubeVideoId = this.getYoutubeVideoId(location); if (youtubeVideoId) { this.bookmarkForm.get('youtubeVideoId').patchValue(youtubeVideoId, {emitEvent: false}); + this.publicBookmarksService.getYoutubeVideoData(youtubeVideoId).subscribe((webpageData: WebpageData) => { + this.patchFormAttributesWithScrapedData(webpageData); + }, + error => { + console.error(`Problems when scraping data for youtube id ${youtubeVideoId}`, error); + this.updateFormWithScrapingDataFromLocation(location); + }); + } else { + const stackoverflowQuestionId = this.getStackoverflowQuestionId(location); + if (stackoverflowQuestionId) { + this.bookmarkForm.get('stackoverflowQuestionId').patchValue(stackoverflowQuestionId, {emitEvent: false}); + this.publicBookmarksService.getStackoverflowQuestionData(stackoverflowQuestionId).subscribe((webpageData: WebpageData) => { + this.patchFormAttributesWithScrapedData(webpageData); + }, + error => { + console.error(`Problems when scraping data for stackoverflow id ${stackoverflowQuestionId}`, error); + this.updateFormWithScrapingDataFromLocation(location); + }); + } else { + this.updateFormWithScrapingDataFromLocation(location); + } } - this.publicBookmarksService.getScrapingData(location, youtubeVideoId).subscribe(response => { - if (response) { - this.bookmarkForm.get('name').patchValue(response.title, {emitEvent: false}); - if (response.publishedOn) { - this.bookmarkForm.get('publishedOn').patchValue(response.publishedOn, {emitEvent: false}); - } - this.bookmarkForm.get('description').patchValue(response.metaDescription, {emitEvent: false}); + } + + private patchFormAttributesWithScrapedData(webpageData) { + if (webpageData.title) { + this.bookmarkForm.get('name').patchValue(webpageData.title, {emitEvent: false}); + } + if (webpageData.publishedOn) { + this.bookmarkForm.get('publishedOn').patchValue(webpageData.publishedOn, {emitEvent: false}); + } + if (webpageData.metaDescription) { + this.bookmarkForm.get('description').patchValue(webpageData.metaDescription, {emitEvent: false}); + } + if (webpageData.tags) { + for (let i = 0; i < webpageData.tags.length; i++) { + const formTags = this.bookmarkForm.get('tags') as FormArray; + formTags.push(this.formBuilder.control(webpageData.tags[i])); } - }); + + this.tagsControl.setValue(null); + this.tags.markAsDirty(); + } } - private getYoutubeVideoId(bookmarkUrl): string { + private updateFormWithScrapingDataFromLocation(location) { + this.publicBookmarksService.getScrapingData(location).subscribe((webpageData: WebpageData) => { + this.patchFormAttributesWithScrapedData(webpageData); + }, + error => { + console.error(`Problems when scraping data for locaation ${location}`, error); + }); + } + + private getYoutubeVideoId(bookmarkUrl): string { let youtubeVideoId = null; - if ( bookmarkUrl.startsWith('https://youtu.be/') ) { + if (bookmarkUrl.startsWith('https://youtu.be/')) { youtubeVideoId = bookmarkUrl.split('/').pop(); - } else if ( bookmarkUrl.startsWith('https://www.youtube.com/watch') ) { + } else if (bookmarkUrl.startsWith('https://www.youtube.com/watch')) { youtubeVideoId = bookmarkUrl.split('v=')[1]; const ampersandPosition = youtubeVideoId.indexOf('&'); - if ( ampersandPosition !== -1 ) { + if (ampersandPosition !== -1) { youtubeVideoId = youtubeVideoId.substring(0, ampersandPosition); } } @@ -155,6 +199,17 @@ export class CreatePersonalBookmarkComponent implements OnInit { return youtubeVideoId; }; + + private getStackoverflowQuestionId(location: string) { + let stackoverflowQuestionId = null; + const regExpMatchArray = location.match(/stackoverflow\.com\/questions\/(\d+)/); + if (regExpMatchArray) { + stackoverflowQuestionId = regExpMatchArray[1]; + } + + return stackoverflowQuestionId; + } + add(event: MatChipInputEvent): void { const input = event.input; const value = event.value; @@ -215,6 +270,10 @@ export class CreatePersonalBookmarkComponent implements OnInit { newBookmark.youtubeVideoId = bookmark.youtubeVideoId; } + if (bookmark.stackoverflowQuestionId) { + newBookmark.stackoverflowQuestionId = bookmark.stackoverflowQuestionId; + } + this.personalBookmarksService.createBookmark(this.userId, newBookmark) .subscribe( res => { @@ -292,6 +351,7 @@ export class CreatePersonalBookmarkComponent implements OnInit { get description() { return this.bookmarkForm.get('description'); } + } diff --git a/src/app/public/bookmarks/public-bookmarks.service.ts b/src/app/public/bookmarks/public-bookmarks.service.ts index a7543e3c..da080505 100644 --- a/src/app/public/bookmarks/public-bookmarks.service.ts +++ b/src/app/public/bookmarks/public-bookmarks.service.ts @@ -29,15 +29,23 @@ export class PublicBookmarksService { return this.httpClient.get(this.publicBookmarksApiBaseUrl, {params: params}); } - getScrapingData(location: string, youtubeVideoId: string): Observable { - let params; - if (youtubeVideoId) { - params = new HttpParams() - .set('youtubeVideoId', youtubeVideoId) - } else { - params = new HttpParams() + getScrapingData(location: string): Observable { + const params = new HttpParams() .set('location', location); - } + return this.httpClient + .get(`${this.publicBookmarksApiBaseUrl}/scrape`, {params: params}); + } + + getYoutubeVideoData(youtubeVideoId: string) { + const params = new HttpParams() + .set('youtubeVideoId', youtubeVideoId) + return this.httpClient + .get(`${this.publicBookmarksApiBaseUrl}/scrape`, {params: params}); + } + + getStackoverflowQuestionData(stackoverflowQuestionId: string) { + const params = new HttpParams() + .set('stackoverflowQuestionId', stackoverflowQuestionId) return this.httpClient .get(`${this.publicBookmarksApiBaseUrl}/scrape`, {params: params}); } diff --git a/src/app/shared/async-bookmark-list.component.html b/src/app/shared/async-bookmark-list.component.html index 7194bec6..2f81d3a8 100644 --- a/src/app/shared/async-bookmark-list.component.html +++ b/src/app/shared/async-bookmark-list.component.html @@ -5,6 +5,7 @@
+
diff --git a/src/app/shared/async-bookmark-list.component.scss b/src/app/shared/async-bookmark-list.component.scss index 62c883e4..9bc3b7ee 100644 --- a/src/app/shared/async-bookmark-list.component.scss +++ b/src/app/shared/async-bookmark-list.component.scss @@ -44,3 +44,7 @@ margin-right: 0.5rem; cursor: pointer; } + +.stackoverflow-icon { + margin-right: 0.5rem; +}