feat: Frontend Dependencies Upgrade +Fix Timestamps in videos

This commit is contained in:
Yiannis Christodoulou
2025-03-18 19:01:50 +02:00
committed by GitHub
parent d1fda05fdc
commit 20f305e69e
72 changed files with 28229 additions and 55880 deletions

View File

@@ -7,9 +7,10 @@ interface MediaListRowProps {
viewAllText?: string;
className?: string;
style?: { [key: string]: any };
children?: React.ReactNode;
}
export const MediaListRow: React.FC<MediaListRowProps> = (props) => {
export const MediaListRow: React.FC<MediaListRowProps> = (props:any) => {
return (
<div className={(props.className ? props.className + ' ' : '') + 'media-list-row'} style={props.style}>
{props.title ? (

View File

@@ -11,7 +11,7 @@ import { replaceString } from '../../utils/helpers/';
import './videojs-markers.js';
import './videojs.markers.css';
import {enableMarkers, addMarker} from './videojs-markers_config.js'
import { enableMarkers, addMarker } from './videojs-markers_config.js';
import { translateString } from '../../utils/helpers/';
import './Comments.scss';
@@ -39,7 +39,7 @@ function CommentForm(props) {
? null
: LinksContext._currentValue.signin +
'?next=/' +
window.location.href.replace(SiteContext._currentValue.url, '').replace(/^\//g, '')
window.location.href.replace(SiteContext._currentValue.url, '').replace(/^\//g, ''),
);
function onFocus() {
@@ -50,12 +50,11 @@ function CommentForm(props) {
setTextareaFocused(false);
}
function onUsersLoad()
{
const userList =[...MediaPageStore.get('users')];
const cleanList = []
userList.forEach(user => {
cleanList.push({id : user.username, display : user.name});
function onUsersLoad() {
const userList = [...MediaPageStore.get('users')];
const cleanList = [];
userList.forEach((user) => {
cleanList.push({ id: user.username, display: user.name });
});
setUsersList(cleanList);
@@ -125,16 +124,14 @@ function CommentForm(props) {
useEffect(() => {
MediaPageStore.on('comment_submit', onCommentSubmit);
MediaPageStore.on('comment_submit_fail', onCommentSubmitFail);
if (MediaCMS.features.media.actions.comment_mention === true)
{
if (MediaCMS.features.media.actions.comment_mention === true) {
MediaPageStore.on('users_load', onUsersLoad);
}
return () => {
MediaPageStore.removeListener('comment_submit', onCommentSubmit);
MediaPageStore.removeListener('comment_submit_fail', onCommentSubmitFail);
if (MediaCMS.features.media.actions.comment_mention === true)
{
if (MediaCMS.features.media.actions.comment_mention === true) {
MediaPageStore.removeListener('users_load', onUsersLoad);
}
};
@@ -146,33 +143,31 @@ function CommentForm(props) {
<UserThumbnail />
<div className="form">
<div className={'form-textarea-wrap' + (textareaFocused ? ' focused' : '')}>
{ MediaCMS.features.media.actions.comment_mention ?
{MediaCMS.features.media.actions.comment_mention ? (
<MentionsInput
inputRef={textareaRef}
className="form-textarea"
rows="1"
placeholder={translateString('Add a ') + commentsText.single + '...'}
placeholder={'Add a ' + commentsText.single + '...'}
value={value}
onChange={onChangeWithMention}
onFocus={onFocus}
onBlur={onBlur}>
<Mention
data={userList}
markup="@(___id___)[___display___]"
/>
onBlur={onBlur}
>
<Mention data={userList} markup="@(___id___)[___display___]" />
</MentionsInput>
:
) : (
<textarea
ref={textareaRef}
className="form-textarea"
rows="1"
placeholder={translateString('Add a ') + commentsText.single + '...'}
placeholder={'Add a ' + commentsText.single + '...'}
value={value}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
></textarea>
}
)}
</div>
<div className="form-buttons">
<button className={'' === value.trim() ? 'disabled' : ''} onClick={submitComment}>
@@ -239,7 +234,9 @@ function CommentActions(props) {
{MemberContext._currentValue.can.deleteComment ? (
<div className="comment-action remove-comment">
<PopupTrigger contentRef={popupContentRef}>
<button>{translateString('DELETE')} {commentsText.uppercaseSingle}</button>
<button>
{translateString('DELETE')} {commentsText.uppercaseSingle}
</button>
</PopupTrigger>
<PopupContent contentRef={popupContentRef}>
@@ -425,7 +422,7 @@ export default function CommentsList(props) {
const [mediaId, setMediaId] = useState(MediaPageStore.get('media-id'));
const [comments, setComments] = useState(
MemberContext._currentValue.can.readComment ? MediaPageStore.get('media-comments') : []
MemberContext._currentValue.can.readComment ? MediaPageStore.get('media-comments') : [],
);
const [displayComments, setDisplayComments] = useState(false);
@@ -433,67 +430,66 @@ export default function CommentsList(props) {
function onCommentsLoad() {
const retrievedComments = [...MediaPageStore.get('media-comments')];
retrievedComments.forEach((comment) => {
comment.text = setTimestampAnchors(comment.text);
});
displayCommentsRelatedAlert();
setComments([...retrievedComments]);
// TODO: this code is breaking, beed ti debug, until then removing the extra
// functionality related with video/timestamp/user mentions
// const video = videojs('vjs_video_3');
// if (MediaCMS.features.media.actions.timestampTimebar)
//{
// enableMarkers(video);
//}
//if (MediaCMS.features.media.actions.comment_mention === true)
//{
// retrievedComments.forEach(comment => {
// comment.text = setMentions(comment.text);
// });
//}
// TODO: this code is breaking
// video.one('loadedmetadata', () => {
// retrievedComments.forEach(comment => {
// comment.text = setTimestampAnchorsAndMarkers(comment.text, video);
// });
// displayCommentsRelatedAlert();
// setComments([...retrievedComments]);
//});
//setComments([...retrievedComments]);
}
function setMentions(text)
{
let sanitizedComment = text.split('@(_').join("<a href=\"/user/");
sanitizedComment = sanitizedComment.split('_)[_').join("\">");
return sanitizedComment.split('_]').join("</a>");
}
function setTimestampAnchorsAndMarkers(text, videoPlayer)
{
function wrapTimestampWithAnchor(match, string)
{
let split = match.split(':'), s = 0, m = 1;
function setTimestampAnchors(text) {
function wrapTimestampWithAnchor(match, string) {
let split = match.split(':'),
s = 0,
m = 1;
let searchParameters = new URLSearchParams(window.location.search);
while (split.length > 0)
{
s += m * parseInt(split.pop(), 10);
m *= 60;
}
if (MediaCMS.features.media.actions.timestampTimebar)
{
addMarker(videoPlayer, s, text);
while (split.length > 0) {
s += m * parseInt(split.pop(), 10);
m *= 60;
}
searchParameters.set('t', s);
searchParameters.set('t', s)
const wrapped = "<a href=\"" + MediaPageStore.get('media-url').split('?')[0] + "?" + searchParameters + "\">" + match + "</a>";
let mediaUrl = MediaPageStore.get('media-url').split('?')[0] + '?' + searchParameters;
const wrapped = '<a href="' + mediaUrl + '">' + match + '</a>';
return wrapped;
}
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
return text.replace(timeRegex , wrapTimestampWithAnchor);
return text.replace(timeRegex, wrapTimestampWithAnchor);
}
function setMentions(text) {
let sanitizedComment = text.split('@(_').join('<a href="/user/');
sanitizedComment = sanitizedComment.split('_)[_').join('">');
return sanitizedComment.split('_]').join('</a>');
}
function setTimestampAnchorsAndMarkers(text, videoPlayer) {
function wrapTimestampWithAnchor(match, string) {
let split = match.split(':'),
s = 0,
m = 1;
let searchParameters = new URLSearchParams(window.location.search);
while (split.length > 0) {
s += m * parseInt(split.pop(), 10);
m *= 60;
}
if (MediaCMS.features.media.actions.timestampTimebar) {
addMarker(videoPlayer, s, text);
}
searchParameters.set('t', s);
const wrapped =
'<a href="' + MediaPageStore.get('media-url').split('?')[0] + '?' + searchParameters + '">' + match + '</a>';
return wrapped;
}
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
return text.replace(timeRegex, wrapTimestampWithAnchor);
}
function onCommentSubmit(commentId) {
@@ -506,7 +502,7 @@ export default function CommentsList(props) {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(
() => PageActions.addNotification(commentsText.ucfirstSingle + ' submission failed', 'commentSubmitFail'),
100
100,
);
}
@@ -520,7 +516,7 @@ export default function CommentsList(props) {
// FIXME: Without delay creates conflict [ Uncaught Error: Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. ].
setTimeout(
() => PageActions.addNotification(commentsText.ucfirstSingle + ' removal failed', 'commentDeleteFail'),
100
100,
);
}
@@ -528,7 +524,7 @@ export default function CommentsList(props) {
setDisplayComments(
comments.length &&
MemberContext._currentValue.can.readComment &&
(MediaPageStore.get('media-data').enable_comments || MemberContext._currentValue.can.editMedia)
(MediaPageStore.get('media-data').enable_comments || MemberContext._currentValue.can.editMedia),
);
}, [comments]);

View File

@@ -520,6 +520,6 @@
};
}
_video2.default.plugin('markers', registerVideoJsMarkersPlugin);
videojs.registerPlugin('markers', registerVideoJsMarkersPlugin);
});
//# sourceMappingURL=videojs-markers.js.map

View File

@@ -14,7 +14,7 @@ function metafield(arr) {
let sep;
let ret = [];
if (arr.length) {
if (arr && arr.length) {
i = 0;
sep = 1 < arr.length ? ', ' : '';
while (i < arr.length) {
@@ -50,7 +50,9 @@ function MediaAuthorBanner(props) {
</a>
</span>
{PageStore.get('config-media-item').displayPublishDate && props.published ? (
<span className="author-banner-date">{translateString("Published on")} {replaceString(publishedOnDate(new Date(props.published)))}</span>
<span className="author-banner-date">
{translateString('Published on')} {replaceString(publishedOnDate(new Date(props.published)))}
</span>
) : null}
</div>
</div>
@@ -78,8 +80,8 @@ function EditMediaButton(props) {
}
return (
<a href={link} rel="nofollow" title={translateString("Edit media")} className="edit-media">
{translateString("EDIT MEDIA")}
<a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media">
{translateString('EDIT MEDIA')}
</a>
);
}
@@ -92,8 +94,8 @@ function EditSubtitleButton(props) {
}
return (
<a href={link} rel="nofollow" title={translateString("Edit subtitle")} className="edit-subtitle">
{translateString("EDIT SUBTITLE")}
<a href={link} rel="nofollow" title={translateString('Edit subtitle')} className="edit-subtitle">
{translateString('EDIT SUBTITLE')}
</a>
);
}
@@ -173,6 +175,28 @@ export default function ViewerInfoContent(props) {
const authorLink = formatInnerLink(props.author.url, SiteContext._currentValue.url);
const authorThumb = formatInnerLink(props.author.thumb, SiteContext._currentValue.url);
function setTimestampAnchors(text) {
function wrapTimestampWithAnchor(match, string) {
let split = match.split(':'),
s = 0,
m = 1;
let searchParameters = new URLSearchParams(window.location.search);
while (split.length > 0) {
s += m * parseInt(split.pop(), 10);
m *= 60;
}
searchParameters.set('t', s);
const wrapped =
'<a href="' + MediaPageStore.get('media-url').split('?')[0] + '?' + searchParameters + '">' + match + '</a>';
return wrapped;
}
const timeRegex = new RegExp('((\\d)?\\d:)?(\\d)?\\d:\\d\\d', 'g');
return text.replace(timeRegex, wrapTimestampWithAnchor);
}
return (
<div className="media-info-content">
{void 0 === PageStore.get('config-media-item').displayAuthor ||
@@ -185,11 +209,10 @@ export default function ViewerInfoContent(props) {
<div className="media-content-banner-inner">
{hasSummary ? <div className="media-content-summary">{summary}</div> : null}
{(!hasSummary || isContentVisible) && description ? (
PageStore.get('config-options').pages.media.htmlInDescription ? (
<div className="media-content-description" dangerouslySetInnerHTML={{ __html: description }}></div>
) : (
<div className="media-content-description">{description}</div>
)
<div
className="media-content-description"
dangerouslySetInnerHTML={{ __html: setTimestampAnchors(description) }}
></div>
) : null}
{hasSummary ? (
<button className="load-more" onClick={onClickLoadMore}>
@@ -197,7 +220,11 @@ export default function ViewerInfoContent(props) {
</button>
) : null}
{tagsContent.length ? (
<MediaMetaField value={tagsContent} title={1 < tagsContent.length ? translateString('Tags') : translateString('Tag')} id="tags" />
<MediaMetaField
value={tagsContent}
title={1 < tagsContent.length ? translateString('Tags') : translateString('Tag')}
id="tags"
/>
) : null}
{categoriesContent.length ? (
<MediaMetaField
@@ -217,7 +244,7 @@ export default function ViewerInfoContent(props) {
) : null}
<PopupTrigger contentRef={popupContentRef}>
<button className="remove-media">{translateString("DELETE MEDIA")}</button>
<button className="remove-media">{translateString('DELETE MEDIA')}</button>
</PopupTrigger>
<PopupContent contentRef={popupContentRef}>

View File

@@ -589,27 +589,46 @@ function findGetParameter(parameterName) {
return result;
}
function handleCanvas(canvasElem) {
const Player = videojs(canvasElem);
function handleCanvas(videoElem) { // Make sure it's a video element
if (!videoElem || !videoElem.tagName || videoElem.tagName.toLowerCase() !== 'video') {
console.error('Invalid video element:', videoElem);
return;
}
const Player = videojs(videoElem);
Player.playsinline(true);
// TODO: Make them work only in embedded player...?
if (findGetParameter('muted') == 1) {
Player.muted(true);
}
if (findGetParameter('time') >= 0) {
Player.currentTime(findGetParameter('time'));
}
if (findGetParameter('autoplay') == 1) {
Player.play();
}
Player.on('loadedmetadata', function () {
const muted = parseInt(findGetParameter('muted'));
const autoplay = parseInt(findGetParameter('autoplay'));
const timestamp = parseInt(findGetParameter('t'));
if (muted == 1) {
Player.muted(true);
}
if (timestamp >= 0 && timestamp < Player.duration()) {
// Start the video from the given time
Player.currentTime(timestamp);
} else if (timestamp >= 0 && timestamp >= Player.duration()) {
// Restart the video if the given time is greater than the duration
Player.play();
}
if (autoplay === 1) {
Player.play();
}
});
}
const observer = new MutationObserver(function (mutations, me) {
const canvas = document.querySelector('.video-js.vjs-mediacms video');
if (canvas) {
handleCanvas(canvas);
me.disconnect();
return;
const observer = new MutationObserver((mutations, me) => {
const playerContainer = document.querySelector('.video-js.vjs-mediacms');
if (playerContainer) {
const video = playerContainer.querySelector('video');
if (video) {
handleCanvas(video);
me.disconnect();
}
}
});

View File

@@ -192,12 +192,14 @@ export function VideoPlayer(props) {
document.addEventListener('visibilitychange', initPlayer);
}
player && player.player.one('loadedmetadata', () => {
/*
// We don't need this because we have a custom function in frontend/src/static/js/components/media-viewer/VideoViewer/index.js:617
player && player.player.one('loadedmetadata', () => {
const urlParams = new URLSearchParams(window.location.search);
const paramT = Number(urlParams.get('t'));
const timestamp = !isNaN(paramT) ? paramT : 0;
player.player.currentTime(timestamp);
});
}); */
return () => {
unsetPlayer();

View File

@@ -1,4 +1,4 @@
import axios, { get as axiosGet, post as axiosPost, put as axiosPut } from 'axios';
import axios from 'axios';
export async function getRequest(url, sync, callback, errorCallback) {
const requestConfig = {
@@ -44,11 +44,11 @@ export async function getRequest(url, sync, callback, errorCallback) {
}
if (sync) {
await axiosGet(url, requestConfig)
await axios.get(url, requestConfig)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axiosGet(url, requestConfig)
axios.get(url, requestConfig)
.then(responseHandler)
.catch(errorHandler || null);
}
@@ -70,11 +70,11 @@ export async function postRequest(url, postData, configData, sync, callback, err
}
if (sync) {
await axiosPost(url, postData, configData || null)
await axios.post(url, postData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axiosPost(url, postData, configData || null)
axios.post(url, postData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
}
@@ -96,11 +96,11 @@ export async function putRequest(url, putData, configData, sync, callback, error
}
if (sync) {
await axiosPut(url, putData, configData || null)
await axios.put(url, putData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axiosPut(url, putData, configData || null)
axios.put(url, putData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
}