readonly VIDEO_TAGS: BuildFormValidator
readonly VIDEO_SUPPORT: BuildFormValidator
readonly VIDEO_SCHEDULE_PUBLICATION_AT: BuildFormValidator
+ readonly VIDEO_ORIGINALLY_PUBLISHED_AT: BuildFormValidator
constructor (private i18n: I18n) {
'required': this.i18n('A date is required to schedule video update.')
}
}
+
+ this.VIDEO_ORIGINALLY_PUBLISHED_AT = {
+ VALIDATORS: [ ],
+ MESSAGES: {}
+ }
}
}
const description = video.description || null
const support = video.support || null
const scheduleUpdate = video.scheduleUpdate || null
+ const originallyPublishedAt = video.originallyPublishedAt || null
return {
name: video.name,
downloadEnabled: video.downloadEnabled,
thumbnailfile: video.thumbnailfile,
previewfile: video.previewfile,
- scheduleUpdate
+ scheduleUpdate,
+ originallyPublishedAt
}
}
uuid?: string
id?: number
scheduleUpdate?: VideoScheduleUpdate
+ originallyPublishedAt?: Date | string
constructor (
video?: Video & {
this.previewUrl = video.previewUrl
this.scheduleUpdate = video.scheduledUpdate
+ this.originallyPublishedAt = new Date(video.originallyPublishedAt)
}
}
} else {
this.scheduleUpdate = null
}
+
+ // Convert originallyPublishedAt to string so that function objectToFormData() works correctly
+ if (this.originallyPublishedAt) {
+ const originallyPublishedAt = new Date(values['originallyPublishedAt'])
+ this.originallyPublishedAt = originallyPublishedAt.toISOString()
+ }
}
toFormPatch () {
downloadEnabled: this.downloadEnabled,
waitTranscoding: this.waitTranscoding,
channelId: this.channelId,
- privacy: this.privacy
+ privacy: this.privacy,
+ originallyPublishedAt: this.originallyPublishedAt
}
// Special case if we scheduled an update
createdAt: Date
updatedAt: Date
publishedAt: Date
+ originallyPublishedAt: Date | string
category: VideoConstant<number>
licence: VideoConstant<number>
language: VideoConstant<string>
this.privacy.label = peertubeTranslate(this.privacy.label, translations)
this.scheduledUpdate = hash.scheduledUpdate
+ this.originallyPublishedAt = hash.originallyPublishedAt ?
+ new Date(hash.originallyPublishedAt.toString())
+ : null
if (this.state) this.state.label = peertubeTranslate(this.state.label, translations)
this.blacklisted = hash.blacklisted
const description = video.description || null
const support = video.support || null
const scheduleUpdate = video.scheduleUpdate || null
+ const originallyPublishedAt = video.originallyPublishedAt || null
const body: VideoUpdate = {
name: video.name,
downloadEnabled: video.downloadEnabled,
thumbnailfile: video.thumbnailfile,
previewfile: video.previewfile,
- scheduleUpdate
+ scheduleUpdate,
+ originallyPublishedAt
}
const data = objectToFormData(body)
</div>
</div>
+ <div class="form-group">
+ <label i18n for="originallyPublishedAt">Original publication date</label>
+ <my-help i18n-preHtml preHtml="This is the date when the content was originally published (e.g. the release date for a film)"></my-help>
+ <p-calendar
+ id="originallyPublishedAt" formControlName="originallyPublishedAt" [dateFormat]="calendarDateFormat"
+ [locale]="calendarLocale" [showTime]="true" [hideOnDateTimeSelect]="true" [monthNavigator]="true" [yearNavigator]="true" [yearRange]="myYearRange"
+ >
+ </p-calendar>
+
+ <div *ngIf="formErrors.originallyPublishedAt" class="form-error">
+ {{ formErrors.originallyPublishedAt }}
+ </div>
+ </div>
+
<my-peertube-checkbox
inputName="nsfw" formControlName="nsfw"
i18n-labelText labelText="This video contains mature or explicit content"
calendarLocale: any = {}
minScheduledDate = new Date()
+ myYearRange = '1880:' + (new Date()).getFullYear()
calendarTimezone: string
calendarDateFormat: string
thumbnailfile: null,
previewfile: null,
support: this.videoValidatorsService.VIDEO_SUPPORT,
- schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT
+ schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT,
+ originallyPublishedAt: this.videoValidatorsService.VIDEO_ORIGINALLY_PUBLISHED_AT
}
this.formValidatorService.updateForm(
<div>
<div class="d-block d-sm-none"> <!-- only shown on small devices, has its conterpart for larger viewports below -->
<h1 class="video-info-name">{{ video.name }}</h1>
-
<div i18n class="video-info-date-views">
Published {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
</div>
<span class="video-attribute-value">{{ video.privacy.label }}</span>
</div>
+ <div *ngIf="!!video.originallyPublishedAt" class="video-attribute">
+ <span i18n class="video-attribute-label">Originally published</span>
+ <span class="video-attribute-value">{{ video.originallyPublishedAt | date: 'dd MMMM yyyy' }}</span>
+ </div>
+
<div class="video-attribute">
<span i18n class="video-attribute-label">Category</span>
<span *ngIf="!video.category.id" class="video-attribute-value">{{ video.category.label }}</span>
margin-bottom: 12px;
.video-attribute-label {
- min-width: 91px;
+ min-width: 142px;
padding-right: 5px;
display: inline-block;
color: $grey-foreground-color;
support: videoInfo.support,
privacy: videoInfo.privacy,
duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
- channelId: res.locals.videoChannel.id
+ channelId: res.locals.videoChannel.id,
+ originallyPublishedAt: videoInfo.originallyPublishedAt
}
const video = new VideoModel(videoData)
video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)
if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.set('downloadEnabled', videoInfoToUpdate.downloadEnabled)
+
+ if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) {
+ videoInstance.set('originallyPublishedAt', videoInfoToUpdate.originallyPublishedAt)
+ }
+
if (videoInfoToUpdate.privacy !== undefined) {
const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10)
videoInstance.set('privacy', newPrivacy)
VIDEO_STATES
} from '../../initializers'
import { VideoModel } from '../../models/video/video'
-import { exists, isArray, isFileValid } from './misc'
+import { exists, isArray, isDateValid, isFileValid } from './misc'
import { VideoChannelModel } from '../../models/video/video-channel'
import { UserModel } from '../../models/account/user'
import * as magnetUtil from 'magnet-uri'
)
}
+function isVideoOriginallyPublishedAtValid (value: string | null) {
+ return value === null || isDateValid(value)
+}
+
function isVideoFileInfoHashValid (value: string | null | undefined) {
return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
}
isVideoTagsValid,
isVideoFPSResolutionValid,
isScheduleVideoUpdatePrivacyValid,
+ isVideoOriginallyPublishedAtValid,
isVideoFile,
isVideoMagnetUriValid,
isVideoStateValid,
// ---------------------------------------------------------------------------
-const LAST_MIGRATION_VERSION = 335
+const LAST_MIGRATION_VERSION = 340
// ---------------------------------------------------------------------------
--- /dev/null
+import * as Sequelize from 'sequelize'
+
+async function up (utils: {
+ transaction: Sequelize.Transaction,
+ queryInterface: Sequelize.QueryInterface,
+ sequelize: Sequelize.Sequelize
+}): Promise<void> {
+
+ {
+ const data = {
+ type: Sequelize.DATE,
+ allowNull: true,
+ defaultValue: Sequelize.NOW
+ }
+ await utils.queryInterface.addColumn('video', 'originallyPublishedAt', data)
+ }
+
+ {
+ const query = 'UPDATE video SET "originallyPublishedAt" = video."publishedAt"'
+ await utils.sequelize.query(query)
+ }
+}
+
+function down (options) {
+ throw new Error('Not implemented.')
+}
+
+export {
+ up,
+ down
+}
} from '../../../helpers/custom-validators/misc'
import {
checkUserCanManageVideo,
+ isVideoOriginallyPublishedAtValid,
isScheduleVideoUpdatePrivacyValid,
isVideoCategoryValid,
isVideoChannelOfAccountExist,
.optional()
.toBoolean()
.custom(isBooleanValid).withMessage('Should have downloading enabled boolean'),
-
+ body('originallyPublishedAt')
+ .optional()
+ .customSanitizer(toValueOrNull)
+ .custom(isVideoOriginallyPublishedAtValid).withMessage('Should have a valid original publication date'),
body('scheduleUpdate')
.optional()
.customSanitizer(toValueOrNull),
createdAt: video.createdAt,
updatedAt: video.updatedAt,
publishedAt: video.publishedAt,
+ originallyPublishedAt: video.originallyPublishedAt,
account: {
id: formattedAccount.id,
uuid: formattedAccount.uuid,
commentsEnabled: video.commentsEnabled,
downloadEnabled: video.downloadEnabled,
published: video.publishedAt.toISOString(),
+ originallyPublishedAt: video.originallyPublishedAt ?
+ video.originallyPublishedAt.toISOString() :
+ null,
updated: video.updatedAt.toISOString(),
mediaType: 'text/markdown',
content: video.getTruncatedDescription(),
{ fields: [ 'createdAt' ] },
{ fields: [ 'publishedAt' ] },
+ { fields: [ 'originallyPublishedAt' ] },
{ fields: [ 'duration' ] },
{ fields: [ 'views' ] },
{ fields: [ 'channelId' ] },
@Column
publishedAt: Date
+ @Column
+ originallyPublishedAt: Date
+
@ForeignKey(() => VideoChannelModel)
@Column
channelId: number
waitTranscoding: boolean
state: VideoState
published: string
+ originallyPublishedAt: string
updated: string
mediaType: 'text/markdown'
content: string
downloadEnabled?: boolean
privacy: VideoPrivacy
scheduleUpdate?: VideoScheduleUpdate
+ originallyPublishedAt: Date | string
}
thumbnailfile?: Blob
previewfile?: Blob
scheduleUpdate?: VideoScheduleUpdate
+ originallyPublishedAt?: Date | string
}
createdAt: Date | string
updatedAt: Date | string
publishedAt: Date | string
+ originallyPublishedAt: Date | string
category: VideoConstant<number>
licence: VideoConstant<number>
language: VideoConstant<string>