Add masto verification link support
This commit is contained in:
parent
5b4c7fc20d
commit
9bacc48643
|
@ -61,7 +61,7 @@
|
|||
<div class="form-group">
|
||||
<label i18n for="instanceDescription">Description</label>
|
||||
<div class="label-small-info">
|
||||
<my-custom-markup-help></my-custom-markup-help>
|
||||
<my-custom-markup-help supportRelMe="true"></my-custom-markup-help>
|
||||
</div>
|
||||
|
||||
<my-markdown-textarea
|
||||
|
|
|
@ -26,10 +26,14 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="description">Description</label>
|
||||
<textarea
|
||||
id="description" formControlName="description" class="form-control"
|
||||
[ngClass]="{ 'input-error': formErrors['description'] }"
|
||||
></textarea>
|
||||
|
||||
<my-help helpType="markdownText" supportRelMe="true"></my-help>
|
||||
|
||||
<my-markdown-textarea
|
||||
inputId="description" formControlName="description" class="form-control"
|
||||
markdownType="enhanced" [formError]="formErrors['description']" withEmoji="true" withHtml="true"
|
||||
></my-markdown-textarea>
|
||||
|
||||
<div *ngIf="formErrors.description" class="form-error" role="alert">
|
||||
{{ formErrors.description }}
|
||||
</div>
|
||||
|
|
|
@ -5,6 +5,8 @@ import { Notifier, User, UserService } from '@app/core'
|
|||
import { USER_DESCRIPTION_VALIDATOR, USER_DISPLAY_NAME_REQUIRED_VALIDATOR } from '@app/shared/form-validators/user-validators'
|
||||
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
|
||||
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
|
||||
import { MarkdownTextareaComponent } from '@app/shared/shared-forms/markdown-textarea.component'
|
||||
import { HelpComponent } from '@app/shared/shared-main/buttons/help.component'
|
||||
import { AlertComponent } from '@app/shared/shared-main/common/alert.component'
|
||||
|
||||
@Component({
|
||||
|
@ -12,7 +14,7 @@ import { AlertComponent } from '@app/shared/shared-main/common/alert.component'
|
|||
templateUrl: './my-account-profile.component.html',
|
||||
styleUrls: [ './my-account-profile.component.scss' ],
|
||||
standalone: true,
|
||||
imports: [ NgIf, FormsModule, ReactiveFormsModule, NgClass, AlertComponent ]
|
||||
imports: [ NgIf, FormsModule, ReactiveFormsModule, NgClass, AlertComponent, HelpComponent, MarkdownTextareaComponent ]
|
||||
})
|
||||
export class MyAccountProfileComponent extends FormReactive implements OnInit {
|
||||
@Input() user: User = null
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
<ng-container i18n>
|
||||
<a class="text-decoration-underline" href="https://en.wikipedia.org/wiki/Markdown#Example" target="_blank" rel="noreferrer noopener">Markdown compatible</a> that also supports <a class="text-decoration-underline" href="https://docs.joinpeertube.org/api/custom-client-markup" target="_blank" rel="noreferrer noopener">custom PeerTube HTML tags</a>
|
||||
<a class="text-decoration-underline" href="https://en.wikipedia.org/wiki/Markdown#Example" target="_blank" rel="noreferrer noopener">Markdown compatible</a> that also supports <a class="text-decoration-underline" href="https://docs.joinpeertube.org/api/custom-client-markup" target="_blank" rel="noreferrer noopener">custom PeerTube HTML tags</a>.
|
||||
</ng-container>
|
||||
|
||||
@if (supportRelMe) {
|
||||
<br />
|
||||
|
||||
<ng-container i18n>
|
||||
<a href="https://docs.joinmastodon.org/user/profile/#verification" target="_blank" rel="noopener noreferrer">Mastodon verification link</a> is also supported.
|
||||
</ng-container>
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component } from '@angular/core'
|
||||
import { booleanAttribute, Component, Input } from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'my-custom-markup-help',
|
||||
|
@ -6,4 +6,5 @@ import { Component } from '@angular/core'
|
|||
standalone: true
|
||||
})
|
||||
export class CustomMarkupHelpComponent {
|
||||
@Input({ transform: booleanAttribute }) supportRelMe = false
|
||||
}
|
||||
|
|
|
@ -54,6 +54,8 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
|
|||
@Input({ required: true }) inputId: string
|
||||
|
||||
@Input() dir: string
|
||||
|
||||
@Input({ transform: booleanAttribute }) withHtml = false
|
||||
@Input({ transform: booleanAttribute }) withEmoji = false
|
||||
|
||||
@ViewChild('textarea') textareaElement: ElementRef
|
||||
|
@ -163,9 +165,9 @@ export class MarkdownTextareaComponent implements ControlValueAccessor, OnInit {
|
|||
|
||||
html = result
|
||||
} else if (this.markdownType === 'text') {
|
||||
html = await this.markdownService.textMarkdownToHTML({ markdown: text, withEmoji: this.withEmoji })
|
||||
html = await this.markdownService.textMarkdownToHTML({ markdown: text, withEmoji: this.withEmoji, withHtml: this.withHtml })
|
||||
} else if (this.markdownType === 'enhanced') {
|
||||
html = await this.markdownService.enhancedMarkdownToHTML({ markdown: text, withEmoji: this.withEmoji })
|
||||
html = await this.markdownService.enhancedMarkdownToHTML({ markdown: text, withEmoji: this.withEmoji, withHtml: this.withHtml })
|
||||
} else if (this.markdownType === 'to-unsafe-html') {
|
||||
html = await this.markdownService.markdownToUnsafeHTML({ markdown: text })
|
||||
}
|
||||
|
|
|
@ -1,9 +1,19 @@
|
|||
import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core'
|
||||
import { NgIf, NgTemplateOutlet } from '@angular/common'
|
||||
import {
|
||||
AfterContentInit,
|
||||
booleanAttribute,
|
||||
Component,
|
||||
ContentChildren,
|
||||
Input,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
QueryList,
|
||||
TemplateRef
|
||||
} from '@angular/core'
|
||||
import { GlobalIconName } from '@app/shared/shared-icons/global-icon.component'
|
||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { ENHANCED_RULES, TEXT_RULES } from '@peertube/peertube-core-utils'
|
||||
import { GlobalIconComponent } from '../../shared-icons/global-icon.component'
|
||||
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgIf, NgTemplateOutlet } from '@angular/common'
|
||||
import { PeerTubeTemplateDirective } from '../common/peertube-template.directive'
|
||||
|
||||
@Component({
|
||||
|
@ -20,6 +30,7 @@ export class HelpComponent implements OnInit, OnChanges, AfterContentInit {
|
|||
@Input() iconName: GlobalIconName = 'help'
|
||||
@Input() title = $localize`Get help`
|
||||
@Input() autoClose = 'outside'
|
||||
@Input({ transform: booleanAttribute }) supportRelMe = false
|
||||
|
||||
@ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'preHtml' | 'customHtml' | 'postHtml'>>
|
||||
|
||||
|
@ -76,9 +87,17 @@ export class HelpComponent implements OnInit, OnChanges, AfterContentInit {
|
|||
}
|
||||
|
||||
private formatMarkdownSupport (rules: string[]) {
|
||||
/* eslint-disable max-len */
|
||||
return $localize`<a href="https://en.wikipedia.org/wiki/Markdown#Example" target="_blank" rel="noopener noreferrer">Markdown</a> compatible that supports:` +
|
||||
let str =
|
||||
// eslint-disable-next-line max-len
|
||||
$localize`<a href="https://en.wikipedia.org/wiki/Markdown#Example" target="_blank" rel="noopener noreferrer">Markdown</a> compatible that supports:` +
|
||||
this.createMarkdownList(rules)
|
||||
|
||||
if (this.supportRelMe) {
|
||||
// eslint-disable-next-line max-len
|
||||
str += $localize`<a href="https://docs.joinmastodon.org/user/profile/#verification" target="_blank" rel="noopener noreferrer">Mastodon verification link</a> is also supported.`
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
private createMarkdownList (rules: string[]) {
|
||||
|
|
|
@ -30,10 +30,8 @@ export class SupportModalComponent {
|
|||
|
||||
const support = this.video?.support || this.videoChannel.support
|
||||
|
||||
this.markdownService.enhancedMarkdownToHTML({ markdown: support })
|
||||
.then(r => {
|
||||
this.htmlSupport = r
|
||||
})
|
||||
this.markdownService.enhancedMarkdownToHTML({ markdown: support, withEmoji: true, withHtml: true })
|
||||
.then(r => this.htmlSupport = r)
|
||||
|
||||
this.displayName = this.video
|
||||
? this.video.channel.displayName
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<div class="form-group">
|
||||
<label i18n for="display-name">Display name</label>
|
||||
<input
|
||||
type="text" id="display-name" class="form-control d-block"
|
||||
type="text" id="display-name" class="form-control"
|
||||
formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
|
||||
>
|
||||
<div *ngIf="formErrors['display-name']" class="form-error" role="alert">
|
||||
|
@ -53,10 +53,15 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="description">Description</label>
|
||||
<textarea
|
||||
id="description" formControlName="description" class="form-control d-block"
|
||||
[ngClass]="{ 'input-error': formErrors['description'] }"
|
||||
></textarea>
|
||||
|
||||
|
||||
<my-help helpType="markdownText" supportRelMe="true"></my-help>
|
||||
|
||||
<my-markdown-textarea
|
||||
inputId="description" formControlName="description" class="form-control"
|
||||
markdownType="enhanced" [formError]="formErrors['description']" withEmoji="true" withHtml="true"
|
||||
></my-markdown-textarea>
|
||||
|
||||
<div *ngIf="formErrors.description" class="form-error" role="alert">
|
||||
{{ formErrors.description }}
|
||||
</div>
|
||||
|
@ -70,8 +75,8 @@
|
|||
></my-help>
|
||||
|
||||
<my-markdown-textarea
|
||||
inputId="support" formControlName="support" class="d-block"
|
||||
markdownType="enhanced" [formError]="formErrors['support']"
|
||||
inputId="support" formControlName="support" class="form-control"
|
||||
markdownType="enhanced" [formError]="formErrors['support']" withEmoji="true" withHtml="true"
|
||||
></my-markdown-textarea>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -161,6 +161,7 @@
|
|||
"memoizee": "^0.4.14",
|
||||
"morgan": "^1.5.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"node-html-parser": "^6.1.13",
|
||||
"node-media-server": "^2.1.4",
|
||||
"nodemailer": "^6.0.0",
|
||||
"opentelemetry-instrumentation-sequelize": "^0.41.0",
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
|
||||
|
||||
import { expect } from 'chai'
|
||||
import { config, expect } from 'chai'
|
||||
import { Account, HttpStatusCode, VideoPlaylistCreateResult } from '@peertube/peertube-models'
|
||||
import { cleanupTests, makeGetRequest, PeerTubeServer } from '@peertube/peertube-server-commands'
|
||||
import { getWatchPlaylistBasePaths, getWatchVideoBasePaths, prepareClientTests } from '@tests/shared/client.js'
|
||||
|
||||
config.truncateThreshold = 0
|
||||
|
||||
describe('Test Open Graph and Twitter cards HTML tags', function () {
|
||||
let servers: PeerTubeServer[]
|
||||
let account: Account
|
||||
|
@ -239,6 +241,51 @@ describe('Test Open Graph and Twitter cards HTML tags', function () {
|
|||
})
|
||||
})
|
||||
|
||||
describe('Mastodon link', function () {
|
||||
|
||||
async function check (path: string, mastoLink: string, exist = true) {
|
||||
const res = await makeGetRequest({ url: servers[0].url, path, accept: 'text/html', expectedStatus: HttpStatusCode.OK_200 })
|
||||
const text = res.text
|
||||
|
||||
const expected = `<link href="${mastoLink}" rel="me">`
|
||||
|
||||
if (exist)expect(text).to.contain(expected)
|
||||
else expect(text).to.not.contain(expected)
|
||||
}
|
||||
|
||||
it('Should correctly include Mastodon link in account', async function () {
|
||||
await servers[0].users.updateMe({
|
||||
description: 'hi, please <a href="https://social.example.com/@username" rel="me">Follow me on Mastodon!</a>'
|
||||
})
|
||||
|
||||
await check('/a/root', 'https://social.example.com/@username')
|
||||
})
|
||||
|
||||
it('Should correctly include Mastodon link in channel', async function () {
|
||||
await servers[0].channels.update({
|
||||
channelName: 'root_channel',
|
||||
attributes: {
|
||||
description: '<a rel="me" href="https://social.example.com/@username2">Follow me on Mastodon!</a>'
|
||||
}
|
||||
})
|
||||
|
||||
await check('/c/root_channel', 'https://social.example.com/@username2')
|
||||
})
|
||||
|
||||
it('Should correctly include Mastodon link on homepage', async function () {
|
||||
await servers[0].config.updateExistingConfig({
|
||||
newConfig: {
|
||||
instance: {
|
||||
description: '<a>toto</a>coucou<a rel="me" href="https://social.example.com/@username3">Follow me on Mastodon!</a>'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await check('/', 'https://social.example.com/@username3')
|
||||
await check('/about', 'https://social.example.com/@username3', false)
|
||||
})
|
||||
})
|
||||
|
||||
after(async function () {
|
||||
await cleanupTests(servers)
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { omit } from '@peertube/peertube-core-utils'
|
||||
import { omit, pick } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
VideoPrivacy,
|
||||
VideoPlaylistPrivacy,
|
||||
|
@ -55,7 +55,7 @@ export async function prepareClientTests () {
|
|||
|
||||
await servers[0].config.updateExistingConfig({
|
||||
newConfig: {
|
||||
instance: { name: instanceConfig.name, shortDescription: instanceConfig.shortDescription }
|
||||
instance: { ...pick(instanceConfig, [ 'name', 'shortDescription' ]) }
|
||||
}
|
||||
})
|
||||
await servers[0].config.updateInstanceImage({ type: ActorImageType.AVATAR, fixture: instanceConfig.avatar })
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Feed } from '@peertube/feed'
|
|||
import { CustomTag, CustomXMLNS, Person } from '@peertube/feed/lib/typings/index.js'
|
||||
import { maxBy, pick } from '@peertube/peertube-core-utils'
|
||||
import { ActorImageType } from '@peertube/peertube-models'
|
||||
import { mdToOneLinePlainText } from '@server/helpers/markdown.js'
|
||||
import { mdToPlainText } from '@server/helpers/markdown.js'
|
||||
import { CONFIG } from '@server/initializers/config.js'
|
||||
import { WEBSERVER } from '@server/initializers/constants.js'
|
||||
import { UserModel } from '@server/models/user/user.js'
|
||||
|
@ -35,7 +35,7 @@ export function initFeed (parameters: {
|
|||
|
||||
return new Feed({
|
||||
title: name,
|
||||
description: mdToOneLinePlainText(description),
|
||||
description: mdToPlainText(description),
|
||||
// updated: TODO: somehowGetLatestUpdate, // optional, default = today
|
||||
id: link || webserverUrl,
|
||||
link: link || webserverUrl,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { VideoIncludeType } from '@peertube/peertube-models'
|
||||
import { mdToOneLinePlainText, toSafeHtml } from '@server/helpers/markdown.js'
|
||||
import { mdToPlainText, toSafeHtml } from '@server/helpers/markdown.js'
|
||||
import { CONFIG } from '@server/initializers/config.js'
|
||||
import { WEBSERVER } from '@server/initializers/constants.js'
|
||||
import { getServerActor } from '@server/models/application/application.js'
|
||||
|
@ -47,7 +47,7 @@ export function getCommonVideoFeedAttributes (video: VideoModel) {
|
|||
return {
|
||||
title: video.name,
|
||||
link: localLink,
|
||||
description: mdToOneLinePlainText(video.getTruncatedDescription()),
|
||||
description: mdToPlainText(video.getTruncatedDescription()),
|
||||
content: toSafeHtml(video.description),
|
||||
|
||||
date: video.publishedAt,
|
||||
|
|
|
@ -29,7 +29,7 @@ const toSafeHtml = (text: string) => {
|
|||
return sanitizeHtml(html, defaultSanitizeOptions)
|
||||
}
|
||||
|
||||
const mdToOneLinePlainText = (text: string) => {
|
||||
const mdToPlainText = (text: string) => {
|
||||
if (!text) return ''
|
||||
|
||||
markdownItForPlainText.render(text)
|
||||
|
@ -42,7 +42,7 @@ const mdToOneLinePlainText = (text: string) => {
|
|||
|
||||
export {
|
||||
toSafeHtml,
|
||||
mdToOneLinePlainText
|
||||
mdToPlainText
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
@ -75,6 +75,7 @@ export class ActorHtml {
|
|||
escapedTitle: escapeHTML(title),
|
||||
escapedSiteName: escapeHTML(siteName),
|
||||
escapedTruncatedDescription,
|
||||
relMe: TagsHtml.findRelMe(entity.description),
|
||||
image,
|
||||
ogType,
|
||||
twitterCard,
|
||||
|
|
|
@ -42,6 +42,10 @@ export class PageHtml {
|
|||
escapedTitle: escapeHTML(CONFIG.INSTANCE.NAME),
|
||||
escapedTruncatedDescription: escapeHTML(CONFIG.INSTANCE.SHORT_DESCRIPTION),
|
||||
|
||||
relMe: url === WEBSERVER.URL
|
||||
? TagsHtml.findRelMe(CONFIG.INSTANCE.DESCRIPTION)
|
||||
: undefined,
|
||||
|
||||
image: avatar
|
||||
? { url: ActorImageModel.getImageUrl(avatar), width: avatar.width, height: avatar.height }
|
||||
: undefined,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { escapeAttribute, escapeHTML } from '@peertube/peertube-core-utils'
|
||||
import { mdToPlainText } from '@server/helpers/markdown.js'
|
||||
import truncate from 'lodash-es/truncate.js'
|
||||
import { CONFIG } from '../../../initializers/config.js'
|
||||
import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, WEBSERVER } from '../../../initializers/constants.js'
|
||||
import { MVideo, MVideoPlaylist } from '../../../types/models/index.js'
|
||||
import { Hooks } from '../../plugins/hooks.js'
|
||||
import truncate from 'lodash-es/truncate.js'
|
||||
import { mdToOneLinePlainText } from '@server/helpers/markdown.js'
|
||||
import { parse } from 'node-html-parser';
|
||||
|
||||
type Tags = {
|
||||
forbidIndexation: boolean
|
||||
|
@ -29,6 +30,8 @@ type Tags = {
|
|||
escapedTitle?: string
|
||||
escapedTruncatedDescription?: string
|
||||
|
||||
relMe?: string
|
||||
|
||||
image?: {
|
||||
url: string
|
||||
width: number
|
||||
|
@ -68,15 +71,25 @@ export class TagsHtml {
|
|||
return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.DESCRIPTION, descriptionTag)
|
||||
}
|
||||
|
||||
static findRelMe (content: string) {
|
||||
if (!content) return undefined
|
||||
|
||||
const html = parse(content)
|
||||
|
||||
return html.querySelector('a[rel=me]')?.getAttribute('href') || undefined
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static async addTags (htmlStringPage: string, tagsValues: Tags, context: HookContext) {
|
||||
const openGraphMetaTags = this.generateOpenGraphMetaTagsOptions(tagsValues)
|
||||
const standardMetaTags = this.generateStandardMetaTagsOptions(tagsValues)
|
||||
const twitterCardMetaTags = this.generateTwitterCardMetaTagsOptions(tagsValues)
|
||||
const metaTags = {
|
||||
...this.generateOpenGraphMetaTagsOptions(tagsValues),
|
||||
...this.generateStandardMetaTagsOptions(tagsValues),
|
||||
...this.generateTwitterCardMetaTagsOptions(tagsValues)
|
||||
}
|
||||
const schemaTags = await this.generateSchemaTagsOptions(tagsValues, context)
|
||||
|
||||
const { url, escapedTitle, oembedUrl, forbidIndexation } = tagsValues
|
||||
const { url, escapedTitle, oembedUrl, forbidIndexation, relMe } = tagsValues
|
||||
|
||||
const oembedLinkTags: { type: string, href: string, escapedTitle: string }[] = []
|
||||
|
||||
|
@ -90,29 +103,12 @@ export class TagsHtml {
|
|||
|
||||
let tagsStr = ''
|
||||
|
||||
// Opengraph
|
||||
Object.keys(openGraphMetaTags).forEach(tagName => {
|
||||
const tagValue = openGraphMetaTags[tagName]
|
||||
if (!tagValue) return
|
||||
for (const tagName of Object.keys(metaTags)) {
|
||||
const tagValue = metaTags[tagName]
|
||||
if (!tagValue) continue
|
||||
|
||||
tagsStr += `<meta property="${tagName}" content="${escapeAttribute(tagValue)}" />`
|
||||
})
|
||||
|
||||
// Standard
|
||||
Object.keys(standardMetaTags).forEach(tagName => {
|
||||
const tagValue = standardMetaTags[tagName]
|
||||
if (!tagValue) return
|
||||
|
||||
tagsStr += `<meta property="${tagName}" content="${escapeAttribute(tagValue)}" />`
|
||||
})
|
||||
|
||||
// Twitter card
|
||||
Object.keys(twitterCardMetaTags).forEach(tagName => {
|
||||
const tagValue = twitterCardMetaTags[tagName]
|
||||
if (!tagValue) return
|
||||
|
||||
tagsStr += `<meta property="${tagName}" content="${escapeAttribute(tagValue)}" />`
|
||||
})
|
||||
}
|
||||
|
||||
// OEmbed
|
||||
for (const oembedLinkTag of oembedLinkTags) {
|
||||
|
@ -125,6 +121,10 @@ export class TagsHtml {
|
|||
tagsStr += `<script type="application/ld+json">${JSON.stringify(schemaTags)}</script>`
|
||||
}
|
||||
|
||||
if (relMe) {
|
||||
tagsStr += `<link href="${escapeAttribute(relMe)}" rel="me">`
|
||||
}
|
||||
|
||||
// SEO, use origin URL
|
||||
if (forbidIndexation !== true && url) {
|
||||
tagsStr += `<link rel="canonical" href="${url}" />`
|
||||
|
@ -261,6 +261,6 @@ export class TagsHtml {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
static buildEscapedTruncatedDescription (description: string) {
|
||||
return truncate(mdToOneLinePlainText(description), { length: 200 })
|
||||
return truncate(mdToPlainText(description), { length: 200 })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8126,6 +8126,14 @@ node-gyp-build@^4.2.0, node-gyp-build@^4.3.0, node-gyp-build@^4.8.2:
|
|||
resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz#8a70ee85464ae52327772a90d66c6077a900cfc8"
|
||||
integrity sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==
|
||||
|
||||
node-html-parser@^6.1.13:
|
||||
version "6.1.13"
|
||||
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.13.tgz#a1df799b83df5c6743fcd92740ba14682083b7e4"
|
||||
integrity sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==
|
||||
dependencies:
|
||||
css-select "^5.1.0"
|
||||
he "1.2.0"
|
||||
|
||||
node-int64@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||
|
|
Loading…
Reference in New Issue