Skip to content

Commit c7a1037

Browse files
committed
feat: add canonical tag to pages with pagination
1 parent a91fd0c commit c7a1037

File tree

7 files changed

+141
-9
lines changed

7 files changed

+141
-9
lines changed

packages/tests/src/client/index-html.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */
22

33
import { expect } from 'chai'
4-
import { HttpStatusCode, VideoPlaylistCreateResult } from '@peertube/peertube-models'
4+
import { HttpStatusCode, ServerConfig, VideoPlaylistCreateResult } from '@peertube/peertube-models'
55
import { cleanupTests, makeGetRequest, makeHTMLRequest, PeerTubeServer } from '@peertube/peertube-server-commands'
66
import { checkIndexTags, getWatchPlaylistBasePaths, getWatchVideoBasePaths, prepareClientTests } from '@tests/shared/client.js'
77

@@ -25,6 +25,8 @@ describe('Test index HTML generation', function () {
2525
shortDescription: string
2626
}
2727

28+
const getTitleWithSuffix = (title: string, config: ServerConfig) => `${title} - ${config.instance.name}`
29+
2830
before(async function () {
2931
this.timeout(120000);
3032

@@ -49,7 +51,7 @@ describe('Test index HTML generation', function () {
4951
const config = await servers[0].config.getConfig()
5052
const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
5153

52-
checkIndexTags(res.text, instanceConfig.name, instanceConfig.shortDescription, '', config)
54+
checkIndexTags(res.text, getTitleWithSuffix('Trending', config), instanceConfig.shortDescription, '', config)
5355
})
5456

5557
it('Should update the customized configuration and have the correct index html tags', async function () {
@@ -73,20 +75,25 @@ describe('Test index HTML generation', function () {
7375
const config = await servers[0].config.getConfig()
7476
const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
7577

76-
checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
78+
checkIndexTags(res.text, getTitleWithSuffix('Trending', config), 'my short description', 'body { background-color: red; }', config)
7779
})
7880

7981
it('Should have valid index html updated tags (title, description...)', async function () {
8082
const config = await servers[0].config.getConfig()
8183
const res = await makeHTMLRequest(servers[0].url, '/videos/trending')
8284

83-
checkIndexTags(res.text, 'PeerTube updated', 'my short description', 'body { background-color: red; }', config)
85+
checkIndexTags(res.text, getTitleWithSuffix('Trending', config), 'my short description', 'body { background-color: red; }', config)
8486
})
8587
})
8688

8789
describe('Canonical tags', function () {
8890

8991
it('Should use the original video URL for the canonical tag', async function () {
92+
const res = await makeHTMLRequest(servers[0].url, '/videos/trending?page=2')
93+
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/videos/trending?page=2" />`)
94+
})
95+
96+
it('Should use pagination in video URL for the canonical tag', async function () {
9097
for (const basePath of getWatchVideoBasePaths()) {
9198
for (const id of videoIds) {
9299
const res = await makeHTMLRequest(servers[0].url, basePath + id)
@@ -114,6 +121,18 @@ describe('Test index HTML generation', function () {
114121
accountURLtest(await makeHTMLRequest(servers[0].url, '/@root@' + servers[0].host))
115122
})
116123

124+
it('Should use pagination in account video channels URL for the canonical tag', async function () {
125+
const res = await makeHTMLRequest(servers[0].url, '/a/root/video-channels?page=2')
126+
127+
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/a/root/video-channels?page=2" />`)
128+
})
129+
130+
it('Should use pagination in account videos URL for the canonical tag', async function () {
131+
const res = await makeHTMLRequest(servers[0].url, '/a/root/videos?page=2')
132+
133+
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/a/root/videos?page=2" />`)
134+
})
135+
117136
it('Should use the original channel URL for the canonical tag', async function () {
118137
const channelURLtests = res => {
119138
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/c/root_channel/videos" />`)
@@ -123,6 +142,19 @@ describe('Test index HTML generation', function () {
123142
channelURLtests(await makeHTMLRequest(servers[0].url, '/c/root_channel@' + servers[0].host))
124143
channelURLtests(await makeHTMLRequest(servers[0].url, '/@root_channel@' + servers[0].host))
125144
})
145+
146+
it('Should use pagination in channel videos URL for the canonical tag', async function () {
147+
const res = await makeHTMLRequest(servers[0].url, '/c/root_channel/videos?page=2')
148+
149+
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/c/root_channel/videos?page=2" />`)
150+
})
151+
152+
it('Should use pagination in channel playlists URL for the canonical tag', async function () {
153+
const res = await makeHTMLRequest(servers[0].url, '/c/root_channel/video-playlists?page=2')
154+
console.log(res.text)
155+
156+
expect(res.text).to.contain(`<link rel="canonical" href="${servers[0].url}/c/root_channel/video-playlists?page=2" />`)
157+
})
126158
})
127159

128160
describe('Indexation tags', function () {

server/core/controllers/client.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { currentDir, root } from '@peertube/peertube-node-utils'
1111
import { STATIC_MAX_AGE } from '../initializers/constants.js'
1212
import { ClientHtml, sendHTML, serveIndexHTML } from '../lib/html/client-html.js'
1313
import { asyncMiddleware, buildRateLimiter, embedCSP } from '../middlewares/index.js'
14+
import { VideosOrderType } from '../lib/html/shared/videos-html.js'
1415

1516
const clientsRouter = express.Router()
1617

@@ -29,6 +30,11 @@ clientsRouter.use([ '/w/p/:id', '/videos/watch/playlist/:id' ],
2930
asyncMiddleware(generateWatchPlaylistHtmlPage)
3031
)
3132

33+
clientsRouter.get([ '/videos/:type(overview|trending|recently-added|local)', '/' ],
34+
clientsRateLimiter,
35+
asyncMiddleware(generateVideosHtmlPage)
36+
)
37+
3238
clientsRouter.use([ '/w/:id', '/videos/watch/:id' ],
3339
clientsRateLimiter,
3440
asyncMiddleware(generateWatchHtmlPage)
@@ -186,6 +192,14 @@ async function generateVideoPlaylistEmbedHtmlPage (req: express.Request, res: ex
186192
return sendHTML(html, res)
187193
}
188194

195+
async function generateVideosHtmlPage (req: express.Request, res: express.Response) {
196+
const { type } = req.params as { type: VideosOrderType }
197+
198+
const html = await ClientHtml.getVideosHTMLPage(type, req, res, req.params.language)
199+
200+
return sendHTML(html, res, true)
201+
}
202+
189203
async function generateWatchHtmlPage (req: express.Request, res: express.Response) {
190204
// Thread link is '/w/:videoId;threadId=:threadId'
191205
// So to get the videoId we need to remove the last part

server/core/lib/html/client-html.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { VideoHtml } from './shared/video-html.js'
66
import { PlaylistHtml } from './shared/playlist-html.js'
77
import { ActorHtml } from './shared/actor-html.js'
88
import { PageHtml } from './shared/page-html.js'
9+
import { VideosHtml, VideosOrderType } from './shared/videos-html.js'
10+
import { CONFIG } from '@server/initializers/config.js'
911

1012
class ClientHtml {
1113

@@ -19,6 +21,20 @@ class ClientHtml {
1921

2022
// ---------------------------------------------------------------------------
2123

24+
static getVideosHTMLPage (type: VideosOrderType, req: express.Request, res: express.Response, paramLang?: string) {
25+
if (type) {
26+
return VideosHtml.getVideosHTML(type, req, res)
27+
}
28+
29+
const [ , eventualType ] = CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE.split('/videos/') as VideosOrderType[]
30+
31+
if (eventualType) {
32+
return VideosHtml.getVideosHTML(eventualType, req, res)
33+
}
34+
35+
return PageHtml.getDefaultHTML(req, res, paramLang)
36+
}
37+
2238
static getWatchHTMLPage (videoIdArg: string, req: express.Request, res: express.Response) {
2339
return VideoHtml.getWatchVideoHTML(videoIdArg, req, res)
2440
}

server/core/lib/html/shared/actor-html.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,25 @@ export class ActorHtml {
5555
let customHTML = TagsHtml.addTitleTag(html, entity.getDisplayName())
5656
customHTML = TagsHtml.addDescriptionTag(customHTML, escapedTruncatedDescription)
5757

58-
const url = entity.getClientUrl()
58+
const eventualPage = req.path.split('/').pop()
59+
let url
60+
61+
if (entity instanceof AccountModel) {
62+
const page = [ 'video-channels', 'videos' ].includes(eventualPage)
63+
? eventualPage
64+
: undefined
65+
url = entity.getClientUrl(page as 'video-channels' | 'videos')
66+
} else if (entity instanceof VideoChannelModel) {
67+
const page = [ 'video-playlists', 'videos' ].includes(eventualPage)
68+
? eventualPage
69+
: undefined
70+
url = entity.getClientUrl(page as 'video-playlists' | 'videos')
71+
}
72+
73+
if (req.query.page) {
74+
url += `?page=${req.query.page}`
75+
}
76+
5977
const siteName = CONFIG.INSTANCE.NAME
6078
const title = entity.getDisplayName()
6179

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { escapeHTML } from '@peertube/peertube-core-utils'
2+
import express from 'express'
3+
import { CONFIG } from '../../../initializers/config.js'
4+
import { WEBSERVER } from '../../../initializers/constants.js'
5+
import { PageHtml } from './page-html.js'
6+
import { TagsHtml } from './tags-html.js'
7+
8+
export type VideosOrderType = 'local' | 'trending' | 'overview' | 'recently-added'
9+
10+
export class VideosHtml {
11+
12+
static async getVideosHTML (type: VideosOrderType, req: express.Request, res: express.Response) {
13+
const html = await PageHtml.getIndexHTML(req, res)
14+
15+
return this.buildVideosHTML({
16+
html,
17+
type,
18+
currentPage: req.query.page
19+
})
20+
}
21+
22+
// ---------------------------------------------------------------------------
23+
// Private
24+
// ---------------------------------------------------------------------------
25+
26+
private static buildVideosHTML (options: {
27+
html: string
28+
type: VideosOrderType
29+
currentPage: string
30+
}) {
31+
const { html, currentPage, type } = options
32+
33+
const title = type === 'recently-added' ? 'Recently added' : type.slice(0, 1).toUpperCase() + type.slice(1)
34+
let customHTML = TagsHtml.addTitleTag(html, title)
35+
customHTML = TagsHtml.addDescriptionTag(customHTML)
36+
37+
let url = WEBSERVER.URL + '/videos/' + type
38+
39+
if (currentPage) {
40+
url += `?page=${currentPage}`
41+
}
42+
43+
return TagsHtml.addTags(customHTML, {
44+
url,
45+
46+
escapedSiteName: escapeHTML(CONFIG.INSTANCE.NAME),
47+
escapedTitle: title,
48+
49+
forbidIndexation: false
50+
}, {})
51+
}
52+
}

server/core/models/account/account.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ export class AccountModel extends SequelizeModel<AccountModel> {
495495
}
496496

497497
// Avoid error when running this method on MAccount... | MChannel...
498-
getClientUrl (this: MAccountHost | MChannelHost) {
499-
return WEBSERVER.URL + '/a/' + this.Actor.getIdentifier() + '/video-channels'
498+
getClientUrl (this: MAccountHost | MChannelHost, page: 'video-channels' | 'videos' = 'video-channels') {
499+
return WEBSERVER.URL + '/a/' + this.Actor.getIdentifier() + '/' + page
500500
}
501501

502502
isBlocked () {

server/core/models/video/video-channel.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -859,8 +859,8 @@ export class VideoChannelModel extends SequelizeModel<VideoChannelModel> {
859859
}
860860

861861
// Avoid error when running this method on MAccount... | MChannel...
862-
getClientUrl (this: MAccountHost | MChannelHost) {
863-
return WEBSERVER.URL + '/c/' + this.Actor.getIdentifier() + '/videos'
862+
getClientUrl (this: MAccountHost | MChannelHost, page: 'video-playlists' | 'videos' = 'videos') {
863+
return WEBSERVER.URL + '/c/' + this.Actor.getIdentifier() + '/' + page
864864
}
865865

866866
getDisplayName () {

0 commit comments

Comments
 (0)