diff options
8 files changed, 58 insertions, 32 deletions
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html index fa4dbb3ca..d847daff7 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="video-info-description"> | 1 | <div class="video-info-description"> |
2 | <div | 2 | <div |
3 | class="video-info-description-html" | 3 | class="video-info-description-html" |
4 | [innerHTML]="videoHTMLDescription" | 4 | [innerHTML]="getHTMLDescription()" |
5 | (timestampClicked)="onTimestampClicked($event)" | 5 | (timestampClicked)="onTimestampClicked($event)" |
6 | myTimestampRouteTransformer | 6 | myTimestampRouteTransformer |
7 | ></div> | 7 | ></div> |
diff --git a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts index 72b09a274..d01080611 100644 --- a/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts +++ b/client/src/app/+videos/+video-watch/shared/metadata/video-description.component.ts | |||
@@ -15,8 +15,10 @@ export class VideoDescriptionComponent implements OnChanges { | |||
15 | 15 | ||
16 | descriptionLoading = false | 16 | descriptionLoading = false |
17 | completeDescriptionShown = false | 17 | completeDescriptionShown = false |
18 | completeVideoDescription: string | 18 | |
19 | shortVideoDescription: string | 19 | completeVideoDescriptionLoaded = false |
20 | |||
21 | videoHTMLTruncatedDescription = '' | ||
20 | videoHTMLDescription = '' | 22 | videoHTMLDescription = '' |
21 | 23 | ||
22 | constructor ( | 24 | constructor ( |
@@ -28,22 +30,19 @@ export class VideoDescriptionComponent implements OnChanges { | |||
28 | ngOnChanges () { | 30 | ngOnChanges () { |
29 | this.descriptionLoading = false | 31 | this.descriptionLoading = false |
30 | this.completeDescriptionShown = false | 32 | this.completeDescriptionShown = false |
31 | this.completeVideoDescription = undefined | ||
32 | 33 | ||
33 | this.setVideoDescriptionHTML() | 34 | this.setVideoDescriptionHTML() |
34 | } | 35 | } |
35 | 36 | ||
36 | showMoreDescription () { | 37 | showMoreDescription () { |
37 | if (this.completeVideoDescription === undefined) { | 38 | if (!this.completeVideoDescriptionLoaded) { |
38 | return this.loadCompleteDescription() | 39 | return this.loadCompleteDescription() |
39 | } | 40 | } |
40 | 41 | ||
41 | this.updateVideoDescription(this.completeVideoDescription) | ||
42 | this.completeDescriptionShown = true | 42 | this.completeDescriptionShown = true |
43 | } | 43 | } |
44 | 44 | ||
45 | showLessDescription () { | 45 | showLessDescription () { |
46 | this.updateVideoDescription(this.shortVideoDescription) | ||
47 | this.completeDescriptionShown = false | 46 | this.completeDescriptionShown = false |
48 | } | 47 | } |
49 | 48 | ||
@@ -56,10 +55,10 @@ export class VideoDescriptionComponent implements OnChanges { | |||
56 | this.completeDescriptionShown = true | 55 | this.completeDescriptionShown = true |
57 | this.descriptionLoading = false | 56 | this.descriptionLoading = false |
58 | 57 | ||
59 | this.shortVideoDescription = this.video.description | 58 | this.video.description = description |
60 | this.completeVideoDescription = description | ||
61 | 59 | ||
62 | this.updateVideoDescription(this.completeVideoDescription) | 60 | this.setVideoDescriptionHTML() |
61 | .catch(err => logger.error(err)) | ||
63 | }, | 62 | }, |
64 | 63 | ||
65 | error: err => { | 64 | error: err => { |
@@ -73,15 +72,25 @@ export class VideoDescriptionComponent implements OnChanges { | |||
73 | this.timestampClicked.emit(timestamp) | 72 | this.timestampClicked.emit(timestamp) |
74 | } | 73 | } |
75 | 74 | ||
76 | private updateVideoDescription (description: string) { | 75 | getHTMLDescription () { |
77 | this.video.description = description | 76 | if (this.completeDescriptionShown) { |
78 | this.setVideoDescriptionHTML() | 77 | return this.videoHTMLDescription |
79 | .catch(err => logger.error(err)) | 78 | } |
79 | |||
80 | return this.videoHTMLTruncatedDescription | ||
80 | } | 81 | } |
81 | 82 | ||
82 | private async setVideoDescriptionHTML () { | 83 | private async setVideoDescriptionHTML () { |
83 | const html = await this.markdownService.textMarkdownToHTML({ markdown: this.video.description }) | 84 | { |
85 | const html = await this.markdownService.textMarkdownToHTML({ markdown: this.video.description }) | ||
84 | 86 | ||
85 | this.videoHTMLDescription = this.markdownService.processVideoTimestamps(this.video.shortUUID, html) | 87 | this.videoHTMLDescription = this.markdownService.processVideoTimestamps(this.video.shortUUID, html) |
88 | } | ||
89 | |||
90 | { | ||
91 | const html = await this.markdownService.textMarkdownToHTML({ markdown: this.video.truncatedDescription }) | ||
92 | |||
93 | this.videoHTMLTruncatedDescription = this.markdownService.processVideoTimestamps(this.video.shortUUID, html) | ||
94 | } | ||
86 | } | 95 | } |
87 | } | 96 | } |
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index c9c6b979c..6fdffb394 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts | |||
@@ -34,6 +34,7 @@ export class Video implements VideoServerModel { | |||
34 | language: VideoConstant<string> | 34 | language: VideoConstant<string> |
35 | privacy: VideoConstant<VideoPrivacy> | 35 | privacy: VideoConstant<VideoPrivacy> |
36 | 36 | ||
37 | truncatedDescription: string | ||
37 | description: string | 38 | description: string |
38 | 39 | ||
39 | duration: number | 40 | duration: number |
@@ -134,6 +135,8 @@ export class Video implements VideoServerModel { | |||
134 | this.privacy = hash.privacy | 135 | this.privacy = hash.privacy |
135 | this.waitTranscoding = hash.waitTranscoding | 136 | this.waitTranscoding = hash.waitTranscoding |
136 | this.state = hash.state | 137 | this.state = hash.state |
138 | |||
139 | this.truncatedDescription = hash.truncatedDescription | ||
137 | this.description = hash.description | 140 | this.description = hash.description |
138 | 141 | ||
139 | this.isLive = hash.isLive | 142 | this.isLive = hash.isLive |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 2a2f008b9..97b3577af 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -7,11 +7,11 @@ import { peertubeTruncate } from '../../core-utils' | |||
7 | import { isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc' | 7 | import { isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc' |
8 | import { isLiveLatencyModeValid } from '../video-lives' | 8 | import { isLiveLatencyModeValid } from '../video-lives' |
9 | import { | 9 | import { |
10 | isVideoDescriptionValid, | ||
10 | isVideoDurationValid, | 11 | isVideoDurationValid, |
11 | isVideoNameValid, | 12 | isVideoNameValid, |
12 | isVideoStateValid, | 13 | isVideoStateValid, |
13 | isVideoTagValid, | 14 | isVideoTagValid, |
14 | isVideoTruncatedDescriptionValid, | ||
15 | isVideoViewsValid | 15 | isVideoViewsValid |
16 | } from '../videos' | 16 | } from '../videos' |
17 | import { isActivityPubUrlValid, isActivityPubVideoDurationValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 17 | import { isActivityPubUrlValid, isActivityPubVideoDurationValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
@@ -32,7 +32,7 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
32 | logger.debug('Video has invalid urls', { video }) | 32 | logger.debug('Video has invalid urls', { video }) |
33 | return false | 33 | return false |
34 | } | 34 | } |
35 | if (!setRemoteVideoTruncatedContent(video)) { | 35 | if (!setRemoteVideoContent(video)) { |
36 | logger.debug('Video has invalid content', { video }) | 36 | logger.debug('Video has invalid content', { video }) |
37 | return false | 37 | return false |
38 | } | 38 | } |
@@ -168,7 +168,7 @@ function isRemoteStringIdentifierValid (data: any) { | |||
168 | } | 168 | } |
169 | 169 | ||
170 | function isRemoteVideoContentValid (mediaType: string, content: string) { | 170 | function isRemoteVideoContentValid (mediaType: string, content: string) { |
171 | return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) | 171 | return mediaType === 'text/markdown' && isVideoDescriptionValid(content) |
172 | } | 172 | } |
173 | 173 | ||
174 | function setValidRemoteIcon (video: any) { | 174 | function setValidRemoteIcon (video: any) { |
@@ -194,9 +194,9 @@ function setValidRemoteVideoUrls (video: any) { | |||
194 | return true | 194 | return true |
195 | } | 195 | } |
196 | 196 | ||
197 | function setRemoteVideoTruncatedContent (video: any) { | 197 | function setRemoteVideoContent (video: any) { |
198 | if (video.content) { | 198 | if (video.content) { |
199 | video.content = peertubeTruncate(video.content, { length: CONSTRAINTS_FIELDS.VIDEOS.TRUNCATED_DESCRIPTION.max }) | 199 | video.content = peertubeTruncate(video.content, { length: CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max }) |
200 | } | 200 | } |
201 | 201 | ||
202 | return true | 202 | return true |
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 3ebfe2937..9e8177f77 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -45,10 +45,6 @@ function isVideoDurationValid (value: string) { | |||
45 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) | 45 | return exists(value) && validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) |
46 | } | 46 | } |
47 | 47 | ||
48 | function isVideoTruncatedDescriptionValid (value: string) { | ||
49 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.TRUNCATED_DESCRIPTION) | ||
50 | } | ||
51 | |||
52 | function isVideoDescriptionValid (value: string) { | 48 | function isVideoDescriptionValid (value: string) { |
53 | return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)) | 49 | return value === null || (exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.DESCRIPTION)) |
54 | } | 50 | } |
@@ -151,7 +147,6 @@ export { | |||
151 | isVideoCategoryValid, | 147 | isVideoCategoryValid, |
152 | isVideoLicenceValid, | 148 | isVideoLicenceValid, |
153 | isVideoLanguageValid, | 149 | isVideoLanguageValid, |
154 | isVideoTruncatedDescriptionValid, | ||
155 | isVideoDescriptionValid, | 150 | isVideoDescriptionValid, |
156 | isVideoFileInfoHashValid, | 151 | isVideoFileInfoHashValid, |
157 | isVideoNameValid, | 152 | isVideoNameValid, |
diff --git a/server/models/video/formatter/video-format-utils.ts b/server/models/video/formatter/video-format-utils.ts index 76745f4b5..240619f69 100644 --- a/server/models/video/formatter/video-format-utils.ts +++ b/server/models/video/formatter/video-format-utils.ts | |||
@@ -103,6 +103,7 @@ function videoModelToFormattedJSON (video: MVideoFormattable, options: VideoForm | |||
103 | }, | 103 | }, |
104 | nsfw: video.nsfw, | 104 | nsfw: video.nsfw, |
105 | 105 | ||
106 | truncatedDescription: video.getTruncatedDescription(), | ||
106 | description: options && options.completeDescription === true | 107 | description: options && options.completeDescription === true |
107 | ? video.description | 108 | ? video.description |
108 | : video.getTruncatedDescription(), | 109 | : video.getTruncatedDescription(), |
@@ -181,6 +182,7 @@ function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): Vid | |||
181 | const span = tracer.startSpan('peertube.VideoModel.toFormattedDetailsJSON') | 182 | const span = tracer.startSpan('peertube.VideoModel.toFormattedDetailsJSON') |
182 | 183 | ||
183 | const videoJSON = video.toFormattedJSON({ | 184 | const videoJSON = video.toFormattedJSON({ |
185 | completeDescription: true, | ||
184 | additionalAttributes: { | 186 | additionalAttributes: { |
185 | scheduledUpdate: true, | 187 | scheduledUpdate: true, |
186 | blacklistInfo: true, | 188 | blacklistInfo: true, |
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts index a4b3ff6e7..c4185882a 100644 --- a/server/tests/api/videos/video-description.ts +++ b/server/tests/api/videos/video-description.ts | |||
@@ -14,8 +14,12 @@ describe('Test video description', function () { | |||
14 | let servers: PeerTubeServer[] = [] | 14 | let servers: PeerTubeServer[] = [] |
15 | let videoUUID = '' | 15 | let videoUUID = '' |
16 | let videoId: number | 16 | let videoId: number |
17 | |||
17 | const longDescription = 'my super description for server 1'.repeat(50) | 18 | const longDescription = 'my super description for server 1'.repeat(50) |
18 | 19 | ||
20 | // 30 characters * 6 -> 240 characters | ||
21 | const truncatedDescription = 'my super description for server 1'.repeat(7) + 'my super descrip...' | ||
22 | |||
19 | before(async function () { | 23 | before(async function () { |
20 | this.timeout(40000) | 24 | this.timeout(40000) |
21 | 25 | ||
@@ -45,15 +49,22 @@ describe('Test video description', function () { | |||
45 | videoUUID = data[0].uuid | 49 | videoUUID = data[0].uuid |
46 | }) | 50 | }) |
47 | 51 | ||
48 | it('Should have a truncated description on each server', async function () { | 52 | it('Should have a truncated description on each server when listing videos', async function () { |
49 | for (const server of servers) { | 53 | for (const server of servers) { |
50 | const video = await server.videos.get({ id: videoUUID }) | 54 | const { data } = await server.videos.list() |
51 | 55 | const video = data.find(v => v.uuid === videoUUID) | |
52 | // 30 characters * 6 -> 240 characters | ||
53 | const truncatedDescription = 'my super description for server 1'.repeat(7) + | ||
54 | 'my super descrip...' | ||
55 | 56 | ||
56 | expect(video.description).to.equal(truncatedDescription) | 57 | expect(video.description).to.equal(truncatedDescription) |
58 | expect(video.truncatedDescription).to.equal(truncatedDescription) | ||
59 | } | ||
60 | }) | ||
61 | |||
62 | it('Should not have a truncated description on each server when getting videos', async function () { | ||
63 | for (const server of servers) { | ||
64 | const video = await server.videos.get({ id: videoUUID }) | ||
65 | |||
66 | expect(video.description).to.equal(longDescription) | ||
67 | expect(video.truncatedDescription).to.equal(truncatedDescription) | ||
57 | } | 68 | } |
58 | }) | 69 | }) |
59 | 70 | ||
diff --git a/shared/models/videos/video.model.ts b/shared/models/videos/video.model.ts index d9765dbd6..06ffb327c 100644 --- a/shared/models/videos/video.model.ts +++ b/shared/models/videos/video.model.ts | |||
@@ -20,7 +20,11 @@ export interface Video { | |||
20 | licence: VideoConstant<number> | 20 | licence: VideoConstant<number> |
21 | language: VideoConstant<string> | 21 | language: VideoConstant<string> |
22 | privacy: VideoConstant<VideoPrivacy> | 22 | privacy: VideoConstant<VideoPrivacy> |
23 | |||
24 | // Deprecated in 5.0 in favour of truncatedDescription | ||
23 | description: string | 25 | description: string |
26 | truncatedDescription: string | ||
27 | |||
24 | duration: number | 28 | duration: number |
25 | isLocal: boolean | 29 | isLocal: boolean |
26 | name: string | 30 | name: string |
@@ -70,7 +74,9 @@ export interface Video { | |||
70 | } | 74 | } |
71 | 75 | ||
72 | export interface VideoDetails extends Video { | 76 | export interface VideoDetails extends Video { |
77 | // Deprecated in 5.0 | ||
73 | descriptionPath: string | 78 | descriptionPath: string |
79 | |||
74 | support: string | 80 | support: string |
75 | channel: VideoChannel | 81 | channel: VideoChannel |
76 | account: Account | 82 | account: Account |