mirror of https://github.com/Chocobozzz/PeerTube
Add links to comment mentions
parent
276d03ed1a
commit
e8cb44090e
|
@ -57,6 +57,7 @@
|
|||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "^1.1.5",
|
||||
"html-webpack-plugin": "^2.19.0",
|
||||
"linkifyjs": "^2.1.5",
|
||||
"lodash-es": "^4.17.4",
|
||||
"markdown-it": "^8.4.0",
|
||||
"ngx-bootstrap": "2.0.2",
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { getAbsoluteAPIUrl } from '@app/shared/misc/utils'
|
||||
import * as linkify from 'linkifyjs'
|
||||
import * as linkifyHtml from 'linkifyjs/html'
|
||||
|
||||
@Injectable()
|
||||
export class LinkifierService {
|
||||
|
||||
static CLASSNAME = 'linkified'
|
||||
|
||||
private linkifyOptions = {
|
||||
className: {
|
||||
mention: LinkifierService.CLASSNAME + '-mention',
|
||||
url: LinkifierService.CLASSNAME + '-url'
|
||||
}
|
||||
}
|
||||
|
||||
constructor () {
|
||||
// Apply plugin
|
||||
this.mentionWithDomainPlugin(linkify)
|
||||
}
|
||||
|
||||
linkify (text: string) {
|
||||
return linkifyHtml(text, this.linkifyOptions)
|
||||
}
|
||||
|
||||
private mentionWithDomainPlugin (linkify: any) {
|
||||
const TT = linkify.scanner.TOKENS // Text tokens
|
||||
const { TOKENS: MT, State } = linkify.parser // Multi tokens, state
|
||||
const MultiToken = MT.Base
|
||||
const S_START = linkify.parser.start
|
||||
|
||||
const TT_AT = TT.AT
|
||||
const TT_DOMAIN = TT.DOMAIN
|
||||
const TT_LOCALHOST = TT.LOCALHOST
|
||||
const TT_NUM = TT.NUM
|
||||
const TT_COLON = TT.COLON
|
||||
const TT_SLASH = TT.SLASH
|
||||
const TT_TLD = TT.TLD
|
||||
const TT_UNDERSCORE = TT.UNDERSCORE
|
||||
const TT_DOT = TT.DOT
|
||||
|
||||
function MENTION (value) {
|
||||
this.v = value
|
||||
}
|
||||
|
||||
linkify.inherits(MultiToken, MENTION, {
|
||||
type: 'mentionWithDomain',
|
||||
isLink: true,
|
||||
toHref () {
|
||||
return getAbsoluteAPIUrl() + '/services/redirect/accounts/' + this.toString().substr(1)
|
||||
}
|
||||
})
|
||||
|
||||
const S_AT = S_START.jump(TT_AT) // @
|
||||
const S_AT_SYMS = new State()
|
||||
const S_MENTION = new State(MENTION)
|
||||
const S_MENTION_DIVIDER = new State()
|
||||
const S_MENTION_DIVIDER_SYMS = new State()
|
||||
|
||||
// @_,
|
||||
S_AT.on(TT_UNDERSCORE, S_AT_SYMS)
|
||||
|
||||
// @_*
|
||||
S_AT_SYMS
|
||||
.on(TT_UNDERSCORE, S_AT_SYMS)
|
||||
.on(TT_DOT, S_AT_SYMS)
|
||||
|
||||
// Valid mention (not made up entirely of symbols)
|
||||
S_AT
|
||||
.on(TT_DOMAIN, S_MENTION)
|
||||
.on(TT_LOCALHOST, S_MENTION)
|
||||
.on(TT_TLD, S_MENTION)
|
||||
.on(TT_NUM, S_MENTION)
|
||||
|
||||
S_AT_SYMS
|
||||
.on(TT_DOMAIN, S_MENTION)
|
||||
.on(TT_LOCALHOST, S_MENTION)
|
||||
.on(TT_TLD, S_MENTION)
|
||||
.on(TT_NUM, S_MENTION)
|
||||
|
||||
// More valid mentions
|
||||
S_MENTION
|
||||
.on(TT_DOMAIN, S_MENTION)
|
||||
.on(TT_LOCALHOST, S_MENTION)
|
||||
.on(TT_TLD, S_MENTION)
|
||||
.on(TT_COLON, S_MENTION)
|
||||
.on(TT_NUM, S_MENTION)
|
||||
.on(TT_UNDERSCORE, S_MENTION)
|
||||
|
||||
// Mention with a divider
|
||||
S_MENTION
|
||||
.on(TT_AT, S_MENTION_DIVIDER)
|
||||
.on(TT_SLASH, S_MENTION_DIVIDER)
|
||||
.on(TT_DOT, S_MENTION_DIVIDER)
|
||||
|
||||
// Mention _ trailing stash plus syms
|
||||
S_MENTION_DIVIDER.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
|
||||
S_MENTION_DIVIDER_SYMS.on(TT_UNDERSCORE, S_MENTION_DIVIDER_SYMS)
|
||||
|
||||
// Once we get a word token, mentions can start up again
|
||||
S_MENTION_DIVIDER
|
||||
.on(TT_DOMAIN, S_MENTION)
|
||||
.on(TT_LOCALHOST, S_MENTION)
|
||||
.on(TT_TLD, S_MENTION)
|
||||
.on(TT_NUM, S_MENTION)
|
||||
|
||||
S_MENTION_DIVIDER_SYMS
|
||||
.on(TT_DOMAIN, S_MENTION)
|
||||
.on(TT_LOCALHOST, S_MENTION)
|
||||
.on(TT_TLD, S_MENTION)
|
||||
.on(TT_NUM, S_MENTION)
|
||||
}
|
||||
}
|
|
@ -59,8 +59,12 @@ export class VideoCommentAddComponent extends FormReactive implements OnInit {
|
|||
|
||||
if (this.parentComment) {
|
||||
const mentions = this.parentComments
|
||||
.filter(c => c.account.id !== this.user.account.id)
|
||||
.map(c => '@' + c.account.name)
|
||||
.filter(c => c.account.id !== this.user.account.id) // Don't add mention of ourselves
|
||||
.map(c => {
|
||||
if (c.account.host) return '@' + c.account.name + '@' + c.account.host
|
||||
|
||||
return c.account.name
|
||||
})
|
||||
|
||||
const mentionsSet = new Set(mentions)
|
||||
const mentionsText = Array.from(mentionsSet).join(' ') + ' '
|
||||
|
|
|
@ -46,10 +46,15 @@
|
|||
.comment-html {
|
||||
word-break: break-all;
|
||||
|
||||
a {
|
||||
/deep/ a {
|
||||
@include disable-default-a-behaviour;
|
||||
|
||||
color: #000;
|
||||
|
||||
// Semi bold mentions
|
||||
&:not(.linkified-url) {
|
||||
font-weight: $font-semibold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'
|
||||
import { MarkdownService } from '@app/videos/shared'
|
||||
import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
|
||||
import * as sanitizeHtml from 'sanitize-html'
|
||||
import { Account as AccountInterface } from '../../../../../../shared/models/actors'
|
||||
import { UserRight } from '../../../../../../shared/models/users'
|
||||
|
@ -31,8 +31,8 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
|||
newParentComments = []
|
||||
|
||||
constructor (
|
||||
private authService: AuthService,
|
||||
private markdownService: MarkdownService
|
||||
private linkifierService: LinkifierService,
|
||||
private authService: AuthService
|
||||
) {}
|
||||
|
||||
get user () {
|
||||
|
@ -93,13 +93,26 @@ export class VideoCommentComponent implements OnInit, OnChanges {
|
|||
}
|
||||
|
||||
private init () {
|
||||
this.sanitizedCommentHTML = sanitizeHtml(this.comment.text, {
|
||||
allowedTags: [ 'a', 'p', 'span', 'br' ],
|
||||
allowedSchemes: [ 'http', 'https' ]
|
||||
})
|
||||
|
||||
// Convert possible markdown to html
|
||||
this.sanitizedCommentHTML = this.markdownService.linkify(this.comment.text)
|
||||
const html = this.linkifierService.linkify(this.comment.text)
|
||||
|
||||
this.sanitizedCommentHTML = sanitizeHtml(html, {
|
||||
allowedTags: [ 'a', 'p', 'span', 'br' ],
|
||||
allowedSchemes: [ 'http', 'https' ],
|
||||
allowedAttributes: {
|
||||
'a': [ 'href', 'class' ]
|
||||
},
|
||||
transformTags: {
|
||||
a: (tagName, attribs) => {
|
||||
return {
|
||||
tagName,
|
||||
attribs: Object.assign(attribs, {
|
||||
target: '_blank'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
this.newParentComments = this.parentComments.concat([ this.comment ])
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { LinkifierService } from '@app/videos/+video-watch/comment/linkifier.service'
|
||||
import { VideoSupportComponent } from '@app/videos/+video-watch/modal/video-support.component'
|
||||
import { TooltipModule } from 'ngx-bootstrap/tooltip'
|
||||
import { ClipboardModule } from 'ngx-clipboard'
|
||||
|
@ -42,6 +43,7 @@ import { VideoWatchComponent } from './video-watch.component'
|
|||
|
||||
providers: [
|
||||
MarkdownService,
|
||||
LinkifierService,
|
||||
VideoCommentService
|
||||
]
|
||||
})
|
||||
|
|
|
@ -5,7 +5,6 @@ import * as MarkdownIt from 'markdown-it'
|
|||
@Injectable()
|
||||
export class MarkdownService {
|
||||
private textMarkdownIt: MarkdownIt.MarkdownIt
|
||||
private linkifier: MarkdownIt.MarkdownIt
|
||||
private enhancedMarkdownIt: MarkdownIt.MarkdownIt
|
||||
|
||||
constructor () {
|
||||
|
@ -27,10 +26,6 @@ export class MarkdownService {
|
|||
.enable('list')
|
||||
.enable('image')
|
||||
this.setTargetToLinks(this.enhancedMarkdownIt)
|
||||
|
||||
this.linkifier = new MarkdownIt('zero', { linkify: true })
|
||||
.enable('linkify')
|
||||
this.setTargetToLinks(this.linkifier)
|
||||
}
|
||||
|
||||
textMarkdownToHTML (markdown: string) {
|
||||
|
@ -45,12 +40,6 @@ export class MarkdownService {
|
|||
return this.avoidTruncatedLinks(html)
|
||||
}
|
||||
|
||||
linkify (text: string) {
|
||||
const html = this.linkifier.render(text)
|
||||
|
||||
return this.avoidTruncatedLinks(html)
|
||||
}
|
||||
|
||||
private setTargetToLinks (markdownIt: MarkdownIt.MarkdownIt) {
|
||||
// Snippet from markdown-it documentation: https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md#renderer
|
||||
const defaultRender = markdownIt.renderer.rules.link_open || function (tokens, idx, options, env, self) {
|
||||
|
|
|
@ -1526,6 +1526,10 @@ copy-webpack-plugin@4.3.0, copy-webpack-plugin@^4.1.1:
|
|||
pify "^3.0.0"
|
||||
serialize-javascript "^1.4.0"
|
||||
|
||||
core-js@^1.0.0:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
|
||||
core-js@^2.4.0, core-js@^2.4.1:
|
||||
version "2.5.3"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.3.tgz#8acc38345824f16d8365b7c9b4259168e8ed603e"
|
||||
|
@ -2098,6 +2102,12 @@ encodeurl@~1.0.1:
|
|||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
|
||||
|
||||
encoding@^0.1.11:
|
||||
version "0.1.12"
|
||||
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb"
|
||||
dependencies:
|
||||
iconv-lite "~0.4.13"
|
||||
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||
|
@ -2574,6 +2584,18 @@ faye-websocket@~0.11.0:
|
|||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
fbjs@^0.8.16:
|
||||
version "0.8.16"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
|
||||
dependencies:
|
||||
core-js "^1.0.0"
|
||||
isomorphic-fetch "^2.1.1"
|
||||
loose-envify "^1.0.0"
|
||||
object-assign "^4.1.0"
|
||||
promise "^7.1.1"
|
||||
setimmediate "^1.0.5"
|
||||
ua-parser-js "^0.7.9"
|
||||
|
||||
figures@^1.3.5:
|
||||
version "1.7.0"
|
||||
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
|
||||
|
@ -3269,7 +3291,7 @@ https-browserify@^1.0.0:
|
|||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
|
||||
|
||||
iconv-lite@0.4.19:
|
||||
iconv-lite@0.4.19, iconv-lite@~0.4.13:
|
||||
version "0.4.19"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b"
|
||||
|
||||
|
@ -3636,7 +3658,7 @@ is-resolvable@^1.0.0:
|
|||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
|
||||
|
||||
is-stream@^1.1.0:
|
||||
is-stream@^1.0.1, is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
|
||||
|
@ -3684,6 +3706,13 @@ isobject@^3.0.0, isobject@^3.0.1:
|
|||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
|
||||
isomorphic-fetch@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9"
|
||||
dependencies:
|
||||
node-fetch "^1.0.1"
|
||||
whatwg-fetch ">=0.10.0"
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
@ -3713,6 +3742,10 @@ istanbul-lib-instrument@^1.7.3:
|
|||
istanbul-lib-coverage "^1.1.1"
|
||||
semver "^5.3.0"
|
||||
|
||||
jquery@>=1.9.0:
|
||||
version "3.3.1"
|
||||
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
|
||||
|
||||
js-base64@^2.1.8, js-base64@^2.1.9:
|
||||
version "2.4.3"
|
||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582"
|
||||
|
@ -3934,6 +3967,14 @@ linkify-it@^2.0.0:
|
|||
dependencies:
|
||||
uc.micro "^1.0.1"
|
||||
|
||||
linkifyjs@^2.1.5:
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-2.1.5.tgz#effc9f01e4aeafbbdbef21a45feab38b9516f93e"
|
||||
optionalDependencies:
|
||||
jquery ">=1.9.0"
|
||||
react ">=0.14.0"
|
||||
react-dom ">=0.14.0"
|
||||
|
||||
load-ip-set@^1.2.7:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-1.3.1.tgz#cfd050c6916e7ba0ca85d0b566e7854713eb495e"
|
||||
|
@ -4122,7 +4163,7 @@ longest@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
|
||||
|
||||
loose-envify@^1.0.0:
|
||||
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
|
||||
dependencies:
|
||||
|
@ -4546,6 +4587,13 @@ node-abi@^2.1.1:
|
|||
dependencies:
|
||||
semver "^5.4.1"
|
||||
|
||||
node-fetch@^1.0.1:
|
||||
version "1.7.3"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
|
||||
dependencies:
|
||||
encoding "^0.1.11"
|
||||
is-stream "^1.0.1"
|
||||
|
||||
node-forge@0.7.1:
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300"
|
||||
|
@ -5487,6 +5535,14 @@ promise@^7.1.1:
|
|||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.6.0:
|
||||
version "15.6.0"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
loose-envify "^1.3.1"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
proxy-addr@~2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec"
|
||||
|
@ -5665,6 +5721,24 @@ rc@^1.1.6, rc@^1.1.7:
|
|||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-dom@>=0.14.0:
|
||||
version "16.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.2.0.tgz#69003178601c0ca19b709b33a83369fe6124c044"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react@>=0.14.0:
|
||||
version "16.2.0"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"
|
||||
dependencies:
|
||||
fbjs "^0.8.16"
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
read-cache@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774"
|
||||
|
@ -6253,7 +6327,7 @@ set-value@^2.0.0:
|
|||
is-plain-object "^2.0.3"
|
||||
split-string "^3.0.1"
|
||||
|
||||
setimmediate@^1.0.4:
|
||||
setimmediate@^1.0.4, setimmediate@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||
|
||||
|
@ -7079,6 +7153,10 @@ typescript@2.6, typescript@~2.6.2:
|
|||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4"
|
||||
|
||||
ua-parser-js@^0.7.9:
|
||||
version "0.7.17"
|
||||
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
|
||||
|
||||
uc.micro@^1.0.1, uc.micro@^1.0.3:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376"
|
||||
|
@ -7597,6 +7675,10 @@ webtorrent@^0.98.0:
|
|||
xtend "^4.0.1"
|
||||
zero-fill "^2.2.3"
|
||||
|
||||
whatwg-fetch@>=0.10.0:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84"
|
||||
|
||||
when@~3.6.x:
|
||||
version "3.6.4"
|
||||
resolved "https://registry.yarnpkg.com/when/-/when-3.6.4.tgz#473b517ec159e2b85005497a13983f095412e34e"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import * as express from 'express'
|
||||
import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers'
|
||||
import { asyncMiddleware, oembedValidator } from '../middlewares'
|
||||
import { accountsNameWithHostGetValidator } from '../middlewares/validators'
|
||||
import { VideoModel } from '../models/video/video'
|
||||
|
||||
const servicesRouter = express.Router()
|
||||
|
@ -9,6 +10,10 @@ servicesRouter.use('/oembed',
|
|||
asyncMiddleware(oembedValidator),
|
||||
generateOEmbed
|
||||
)
|
||||
servicesRouter.use('/redirect/accounts/:nameWithHost',
|
||||
asyncMiddleware(accountsNameWithHostGetValidator),
|
||||
redirectToAccountUrl
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
@ -62,3 +67,7 @@ function generateOEmbed (req: express.Request, res: express.Response, next: expr
|
|||
|
||||
return res.json(json)
|
||||
}
|
||||
|
||||
function redirectToAccountUrl (req: express.Request, res: express.Response, next: express.NextFunction) {
|
||||
return res.redirect(res.locals.account.Actor.url)
|
||||
}
|
||||
|
|
|
@ -31,6 +31,16 @@ function isLocalAccountNameExist (name: string, res: Response) {
|
|||
return isAccountExist(promise, res)
|
||||
}
|
||||
|
||||
function isAccountNameWithHostExist (nameWithDomain: string, res: Response) {
|
||||
const [ accountName, host ] = nameWithDomain.split('@')
|
||||
|
||||
let promise: Bluebird<AccountModel>
|
||||
if (!host) promise = AccountModel.loadLocalByName(accountName)
|
||||
else promise = AccountModel.loadLocalByNameAndHost(accountName, host)
|
||||
|
||||
return isAccountExist(promise, res)
|
||||
}
|
||||
|
||||
async function isAccountExist (p: Bluebird<AccountModel>, res: Response) {
|
||||
const account = await p
|
||||
|
||||
|
@ -53,5 +63,6 @@ export {
|
|||
isAccountIdExist,
|
||||
isLocalAccountNameExist,
|
||||
isAccountDescriptionValid,
|
||||
isAccountNameWithHostExist,
|
||||
isAccountNameValid
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ const logger = new winston.createLogger({
|
|||
)
|
||||
}),
|
||||
new winston.transports.Console({
|
||||
handleExcegiptions: true,
|
||||
handleExceptions: true,
|
||||
humanReadableUnhandledException: true,
|
||||
format: winston.format.combine(
|
||||
timestampFormatter,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import * as express from 'express'
|
||||
import { param } from 'express-validator/check'
|
||||
import { isAccountIdExist, isAccountNameValid, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts'
|
||||
import {
|
||||
isAccountIdExist,
|
||||
isAccountNameValid,
|
||||
isAccountNameWithHostExist,
|
||||
isLocalAccountNameExist
|
||||
} from '../../helpers/custom-validators/accounts'
|
||||
import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc'
|
||||
import { logger } from '../../helpers/logger'
|
||||
import { areValidationErrors } from './utils'
|
||||
|
@ -31,9 +36,23 @@ const accountsGetValidator = [
|
|||
}
|
||||
]
|
||||
|
||||
const accountsNameWithHostGetValidator = [
|
||||
param('nameWithHost').exists().withMessage('Should have an account name with host'),
|
||||
|
||||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
logger.debug('Checking accountsNameWithHostGetValidator parameters', { parameters: req.params })
|
||||
|
||||
if (areValidationErrors(req, res)) return
|
||||
if (!await isAccountNameWithHostExist(req.params.nameWithHost, res)) return
|
||||
|
||||
return next()
|
||||
}
|
||||
]
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
localAccountValidator,
|
||||
accountsGetValidator
|
||||
accountsGetValidator,
|
||||
accountsNameWithHostGetValidator
|
||||
}
|
||||
|
|
|
@ -157,7 +157,6 @@ export class AccountModel extends Model<AccountModel> {
|
|||
static loadLocalByName (name: string) {
|
||||
const query = {
|
||||
where: {
|
||||
name,
|
||||
[ Sequelize.Op.or ]: [
|
||||
{
|
||||
userId: {
|
||||
|
@ -170,8 +169,42 @@ export class AccountModel extends Model<AccountModel> {
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
where: {
|
||||
preferredUsername: name
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return AccountModel.findOne(query)
|
||||
}
|
||||
|
||||
static loadLocalByNameAndHost (name: string, host: string) {
|
||||
const query = {
|
||||
include: [
|
||||
{
|
||||
model: ActorModel,
|
||||
required: true,
|
||||
where: {
|
||||
preferredUsername: name
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: ServerModel,
|
||||
required: true,
|
||||
where: {
|
||||
host
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
return AccountModel.findOne(query)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue