mirror of
https://github.com/videojs/video.js.git
synced 2024-12-31 03:11:11 +02:00
Attempting new UI organization.
This commit is contained in:
parent
193172498c
commit
a314c0a242
@ -59,14 +59,14 @@
|
||||
<tr><th colspan="2">HTML5</th><th colspan="2">H5Swf</th><th colspan="2">YouTube</th></tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<video id="vid1" class="video-js vjs-default-skin" controls preload="none" width="480" height="198"
|
||||
<video id="vid1" class="video-js vjs-default-skin" controls preload="auto" width="480" height="198"
|
||||
poster="http://video-js.zencoder.com/oceans-clip.png">
|
||||
<source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4'>
|
||||
<!-- <source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm'> -->
|
||||
<source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm'>
|
||||
</video>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<video id="vid2" class="video-js vjs-default-skin" controls preload="none" width="480" height="198"
|
||||
<video id="vid2" class="video-js vjs-default-skin" controls preload="auto" width="480" height="198"
|
||||
poster="http://video-js.zencoder.com/oceans-clip.png">
|
||||
<source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4'>
|
||||
<!-- <source src="http://video-js.zencoder.com/oceans-clip.webm" type='video/webm'> -->
|
||||
|
@ -1,7 +1,7 @@
|
||||
$(function(){
|
||||
var tech, i,
|
||||
techList = ["html5","h5swf"],
|
||||
props = "error,currentSrc,networkState,buffered,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoWidth,videoHeight,textTracks,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,controls,volume,muted,defaultMuted".split(","),
|
||||
props = "error,currentSrc,networkState,buffered,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoWidth,videoHeight,textTracks,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,controls,volume,muted,defaultMuted,poster".split(","),
|
||||
methods = "play,pause,src,load,canPlayType,addTextTrack",
|
||||
notUsed = "mediaGroup,controller,videoTracks,audioTracks,defaultPlaybackRate";
|
||||
|
||||
@ -16,8 +16,6 @@ $(function(){
|
||||
var eventsId = "#"+this+"_events",
|
||||
type = evt.type,
|
||||
prev = $(eventsId+" div").first();
|
||||
|
||||
if (type == 'error') _V_.log(evt);
|
||||
|
||||
if (prev && prev.html() && prev.html().indexOf(type + " ") === 0) {
|
||||
var countSpan = prev.children(".count");
|
||||
@ -47,7 +45,6 @@ $(function(){
|
||||
result = (result.length > 0) ? "s:"+result.start(0)+" e:"+result.end(0) : "-";
|
||||
}
|
||||
} catch(e) {
|
||||
_V_.log(e);
|
||||
result = "<span class='na'>N/A</span>";
|
||||
}
|
||||
$("#"+this.currentTechName+prop).html(result);
|
||||
|
@ -19,6 +19,8 @@
|
||||
|
||||
<script src="src/log.js"></script>
|
||||
|
||||
<script src="src/ui.js"></script>
|
||||
|
||||
<script src="src/api.js"></script>
|
||||
<script src="src/events.js"></script>
|
||||
|
||||
@ -47,7 +49,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<video id="vid1" class="video-js vjs-default-skin" autoplay preload="auto" width="640" height="264"
|
||||
<video id="vid1" class="video-js vjs-default-skin" controls preload="none" width="640" height="264"
|
||||
poster="http://video-js.zencoder.com/oceans-clip.png"
|
||||
data-setup='{"techOrder":["html5","h5swf","youtube"]}'>
|
||||
<source src="http://video-js.zencoder.com/oceans-clip.mp4" type='video/mp4'>
|
||||
|
Binary file not shown.
@ -117,7 +117,7 @@ VideoJS.fn.extend({
|
||||
// Turn on fullscreen (or window) mode
|
||||
enterFullScreen: function(){
|
||||
if (this.supportsFullScreen()) {
|
||||
this.api("enterFullScreen");
|
||||
this.apiCall("enterFullScreen");
|
||||
} else {
|
||||
this.enterFullWindow();
|
||||
}
|
||||
@ -142,10 +142,7 @@ VideoJS.fn.extend({
|
||||
this.docOrigOverflow = document.documentElement.style.overflow;
|
||||
|
||||
// Add listener for esc key to exit fullscreen
|
||||
_V_.addEvent(document, "keydown", _V_.proxy(this, this.fullscreenOnEscKey));
|
||||
|
||||
// Add listener for a window resize
|
||||
_V_.addEvent(window, "resize", _V_.proxy(this, this.fullscreenOnWindowResize));
|
||||
_V_.addEvent(document, "keydown", _V_.proxy(this, this.fullWindowOnEscKey));
|
||||
|
||||
// Hide any scroll bars
|
||||
document.documentElement.style.overflow = 'hidden';
|
||||
@ -156,11 +153,16 @@ VideoJS.fn.extend({
|
||||
|
||||
this.triggerEvent("enterFullWindow");
|
||||
},
|
||||
|
||||
fullWindowOnEscKey: function(event){
|
||||
if (event.keyCode == 27) {
|
||||
this.exitFullScreen();
|
||||
}
|
||||
},
|
||||
|
||||
exitFullWindow: function(){
|
||||
this.videoIsFullScreen = false;
|
||||
_V_.removeEvent(document, "keydown", this.fullscreenOnEscKey);
|
||||
_V_.removeEvent(window, "resize", this.fullscreenOnWindowResize);
|
||||
_V_.removeEvent(document, "keydown", this.fullWindowOnEscKey);
|
||||
|
||||
// Unhide scroll bars.
|
||||
document.documentElement.style.overflow = this.docOrigOverflow;
|
||||
@ -272,6 +274,7 @@ VideoJS.fn.extend({
|
||||
|
||||
controls: function(){ return this.options.controls; },
|
||||
textTracks: function(){ return this.options.tracks; },
|
||||
poster: function(){ return this.apiCall("poster"); },
|
||||
|
||||
error: function(){ return this.apiCall("error"); },
|
||||
networkState: function(){ return this.apiCall("networkState"); },
|
||||
|
@ -96,10 +96,10 @@ VideoJS.fn.newBehavior("seekBar",
|
||||
},
|
||||
updateSeekBars: function(){
|
||||
// If scrubbing, use the cached currentTime value for speed
|
||||
var progress = /* (this.scrubbing) ? this.scrubTime / this.duration() : */ this.currentTime() / this.duration();
|
||||
var progress = /* (this.scrubbing) ? this.values.currentTime / this.duration() : */ this.currentTime() / this.duration();
|
||||
// Protect against no duration and other division issues
|
||||
if (isNaN(progress)) { progress = 0; }
|
||||
|
||||
|
||||
this.each(this.bels.seekBars, function(bar){
|
||||
var barData = _V_.getData(bar),
|
||||
barX = _V_.findPosX(bar),
|
||||
@ -165,33 +165,36 @@ VideoJS.fn.newBehavior("seekHandle",
|
||||
{}
|
||||
);
|
||||
|
||||
/* Play Progress Bar Behaviors
|
||||
/*
|
||||
CHANGED TO SEEK BAR
|
||||
Play Progress Bar Behaviors
|
||||
================================================================================ */
|
||||
VideoJS.fn.newBehavior("playProgressBar",
|
||||
function(element){
|
||||
if (!this.bels.playProgressBars) {
|
||||
this.bels.playProgressBars = [];
|
||||
this.addEvent("timeupdate", this.updatePlayProgressBars);
|
||||
}
|
||||
this.bels.playProgressBars.push(element);
|
||||
},
|
||||
function(){
|
||||
// Remove
|
||||
},
|
||||
{
|
||||
// Ajust the play progress bar's width based on the current play time
|
||||
updatePlayProgressBars: function(){
|
||||
// If scrubbing, use the cached currentTime value for speed
|
||||
var progress = (this.scrubbing) ? this.values.currentTime / this.duration() : this.currentTime() / this.duration();
|
||||
// Protect against no duration and other division issues
|
||||
if (isNaN(progress)) { progress = 0; }
|
||||
// Update bar length
|
||||
this.each(this.bels.playProgressBars, function(bar){
|
||||
if (bar.style) { bar.style.width = _V_.round(progress * 100, 2) + "%"; }
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
// VideoJS.fn.newBehavior("playProgressBar",
|
||||
// function(element){
|
||||
// if (!this.bels.playProgressBars) {
|
||||
// this.bels.playProgressBars = [];
|
||||
// this.addEvent("timeupdate", this.updatePlayProgressBars);
|
||||
// }
|
||||
// this.bels.playProgressBars.push(element);
|
||||
// },
|
||||
// function(){
|
||||
// // Remove
|
||||
// },
|
||||
// {
|
||||
// // Ajust the play progress bar's width based on the current play time
|
||||
// updatePlayProgressBars: function(){
|
||||
// // If scrubbing, use the cached currentTime value for speed
|
||||
// var progress = (this.scrubbing) ? this.values.currentTime / this.duration() : this.currentTime() / this.duration();
|
||||
// _V_.log("PROG", progress)
|
||||
// // Protect against no duration and other division issues
|
||||
// if (isNaN(progress)) { progress = 0; }
|
||||
// // Update bar length
|
||||
// this.each(this.bels.playProgressBars, function(bar){
|
||||
// if (bar.style) { bar.style.width = _V_.round(progress * 100, 2) + "%"; }
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
|
||||
/* Load Progress Bar Behaviors
|
||||
================================================================================ */
|
||||
|
@ -104,6 +104,9 @@ var VideoJS = _V_ = function(id, addOptions, ready){
|
||||
// this.cels.mainControls.playButton = playButtonDiv;
|
||||
this.cels = {};
|
||||
|
||||
// New holder for UI objects
|
||||
this.ui = {};
|
||||
|
||||
// Cache for video property values.
|
||||
this.values = {};
|
||||
|
||||
@ -117,8 +120,12 @@ var VideoJS = _V_ = function(id, addOptions, ready){
|
||||
if (this.options.controls) {
|
||||
this.addEvent("techready", function(){
|
||||
this.each(this.options.controlSets, function(set){
|
||||
_V_.controlSets[set].add.call(this);
|
||||
this.appendUI(set);
|
||||
});
|
||||
|
||||
// this.each(this.options.controlSets, function(set){
|
||||
// _V_.controlSets[set].add.call(this);
|
||||
// });
|
||||
});
|
||||
}
|
||||
|
||||
@ -133,7 +140,8 @@ var VideoJS = _V_ = function(id, addOptions, ready){
|
||||
|
||||
VideoJS.options = {
|
||||
techOrder: ["html5","h5swf","flowplayer"],
|
||||
controlSets: ["bigPlayButton", "bar", "subtitlesBox"/*, "replay"*/],
|
||||
// controlSets: ["bigPlayButton", "bar", "subtitlesBox"/*, "replay"*/],
|
||||
controlSets: ["bigPlayButton", "controlBar"/*, "bar", "subtitlesBox", "replay"*/],
|
||||
controlSetOptions: {
|
||||
bigPlayButton: {},
|
||||
bar: {},
|
||||
@ -286,6 +294,29 @@ VideoJS.fn = VideoJS.prototype = {
|
||||
if (typeof element == "string") { element = _V_.el(element); }
|
||||
this.behaviors[behavior].remove.call(this, element);
|
||||
},
|
||||
|
||||
appendUI: function(nameORobj){
|
||||
var name, uiClass, options, ui;
|
||||
|
||||
if (typeof set == "string") {
|
||||
name = nameORobj;
|
||||
|
||||
// Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
|
||||
uiClass = _V_.capitalize(nameORobj);
|
||||
|
||||
// Can also pass in object to define a different class than the name and add other options
|
||||
} else {
|
||||
name = nameORobj.name;
|
||||
uiClass = nameORobj.uiClass;
|
||||
options = nameORobj.options;
|
||||
}
|
||||
|
||||
// Create a new object & element for this controls set
|
||||
ui = this.ui[name] = new _V_[uiClass](this);
|
||||
|
||||
// Add the UI object's element to the container div (box)
|
||||
this.box.appendChild(ui.el);
|
||||
},
|
||||
|
||||
/* Fallbacks for unsupported event types
|
||||
================================================================================ */
|
||||
@ -380,9 +411,9 @@ VideoJS.fn = VideoJS.prototype = {
|
||||
this.currentTime(0);
|
||||
this.play();
|
||||
} else {
|
||||
this.pause();
|
||||
this.currentTime(0);
|
||||
this.pause();
|
||||
// this.pause();
|
||||
// this.currentTime(0);
|
||||
// this.pause();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -119,6 +119,10 @@ _V_.extend({
|
||||
return h + m + s;
|
||||
},
|
||||
|
||||
capitalize: function(string){
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
},
|
||||
|
||||
// Return the relative horizonal position of an event as a value from 0-1
|
||||
getRelativePosition: function(x, relativeElement){
|
||||
return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth));
|
||||
|
@ -38,7 +38,8 @@ VideoJS.tech.h5swf = {
|
||||
autoplay: this.options.autoplay,
|
||||
preload: this.options.preload,
|
||||
loop: this.options.loop,
|
||||
muted: this.options.muted
|
||||
muted: this.options.muted,
|
||||
poster: this.options.poster,
|
||||
},
|
||||
|
||||
params = {
|
||||
@ -105,6 +106,7 @@ VideoJS.tech.h5swf = {
|
||||
pause: function(){ this.tels.h5swf.vjs_pause(); },
|
||||
src: function(src){ this.tels.h5swf.vjs_src(src); },
|
||||
load: function(){ this.tels.h5swf.vjs_load(); },
|
||||
poster: function(){ this.tels.h5swf.vjs_getProperty("poster"); },
|
||||
|
||||
buffered: function(){
|
||||
return _V_.createTimeRange(0, this.tels.h5swf.vjs_getProperty("buffered"));
|
||||
|
262
dev/src/ui.js
Normal file
262
dev/src/ui.js
Normal file
@ -0,0 +1,262 @@
|
||||
(function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; _V_.Class = function(){}; _V_.Class.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments); } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class;};})();
|
||||
|
||||
// bar.playToggle = new _V_.PlayToggle(player, options);
|
||||
|
||||
/* UI Component- Base class for all UI objects
|
||||
================================================================================ */
|
||||
_V_.UIComponent = _V_.Class.extend({
|
||||
init: function(player, options){
|
||||
this.player = player;
|
||||
|
||||
// Array of sub-components
|
||||
this.components = [];
|
||||
},
|
||||
createElement: function(){},
|
||||
destroy: function(){},
|
||||
|
||||
// Add child components to this component.
|
||||
// Will generate a new child component and then append child component's element to this component's element.
|
||||
// Takes either the name of the UI component class, or an object that contains a name, UI Class, and options.
|
||||
addComponent: function(nameORobject){
|
||||
var name, uiClass, options, component;
|
||||
|
||||
if (typeof set == "string") {
|
||||
name = nameORobj;
|
||||
|
||||
// Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
|
||||
uiClass = _V_.capitalize(nameORobj);
|
||||
|
||||
// Can also pass in object to define a different class than the name and add other options
|
||||
} else {
|
||||
name = nameORobj.name;
|
||||
uiClass = nameORobj.uiClass;
|
||||
options = nameORobj.options;
|
||||
}
|
||||
|
||||
// Create a new object & element for this controls set
|
||||
component = this.ui[name] = new _V_[uiClass](this);
|
||||
|
||||
// Add the UI object's element to the container div (box)
|
||||
this.el.appendChild(component.el);
|
||||
},
|
||||
|
||||
show: function(){
|
||||
this.el.style.display = "block";
|
||||
},
|
||||
|
||||
hide: function(){
|
||||
this.el.style.display = "none";
|
||||
}
|
||||
});
|
||||
|
||||
/* Button - Base class for all buttons
|
||||
================================================================================ */
|
||||
_V_.Button = _V_.UIComponent.extend({
|
||||
|
||||
init: function(player, options){
|
||||
|
||||
this.player = player;
|
||||
this.el = this.createElement();
|
||||
|
||||
_V_.addEvent(this.el, "click", _V_.proxy(this, this.onClick));
|
||||
_V_.addEvent(this.el, "focus", _V_.proxy(this, this.onFocus));
|
||||
_V_.addEvent(this.el, "blur", _V_.proxy(this, this.onBlur));
|
||||
|
||||
this._super(player, options);
|
||||
},
|
||||
|
||||
createElement: function(type, options){
|
||||
_V_.merge({
|
||||
role: "button",
|
||||
tabIndex: 0
|
||||
}, options || {})
|
||||
return _V_.createElement(type || "div", options);
|
||||
},
|
||||
|
||||
// Click - Override with specific functionality for button
|
||||
onClick: function(){},
|
||||
|
||||
// Focus - Add keyboard functionality to element
|
||||
onFocus: function(){
|
||||
_V_.addEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
|
||||
},
|
||||
|
||||
// KeyPress (document level) - Trigger click when keys are pressed
|
||||
onKeyPress: function(event){
|
||||
// Check for space bar (32) or enter (13) keys
|
||||
if (event.which == 32 || event.which == 13) {
|
||||
event.preventDefault();
|
||||
this.onClick();
|
||||
}
|
||||
},
|
||||
|
||||
// Blur - Remove keyboard triggers
|
||||
onBlur: function(){
|
||||
_V_.removeEvent(document, "keyup", _V_.proxy(this, this.onKeyPress));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/* Play Button
|
||||
================================================================================ */
|
||||
_V_.PlayButton = _V_.Button.extend({
|
||||
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
className: "vjs-play-button vjs-control",
|
||||
innerHTML: '<div><span class="vjs-control-text">Play</span></div>'
|
||||
});
|
||||
},
|
||||
|
||||
onClick: function(){
|
||||
this.player.play();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/* Pause Button
|
||||
================================================================================ */
|
||||
_V_.PauseButton = _V_.Button.extend({
|
||||
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
className: "vjs-pause-button vjs-control",
|
||||
innerHTML: '<div><span class="vjs-control-text">Pause</span></div>'
|
||||
});
|
||||
},
|
||||
|
||||
onClick: function(){
|
||||
this.player.pause();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/* Play Toggle - Play or Pause Media
|
||||
================================================================================ */
|
||||
_V_.PlayToggle = _V_.Button.extend({
|
||||
|
||||
init: function(player, options){
|
||||
|
||||
player.addEvent("play", _V_.proxy(this, this.onPlay));
|
||||
player.addEvent("pause", _V_.proxy(this, this.onPause));
|
||||
|
||||
return this._super(player, options);
|
||||
},
|
||||
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
className: "vjs-play-control vjs-control",
|
||||
innerHTML: '<div><span class="vjs-control-text">Play</span></div>'
|
||||
});
|
||||
},
|
||||
|
||||
// OnClick - Toggle between play and pause
|
||||
onClick: function(){
|
||||
if (this.player.paused()) {
|
||||
this.player.play();
|
||||
} else {
|
||||
this.player.pause();
|
||||
}
|
||||
},
|
||||
|
||||
// OnPlay - Add the vjs-playing class to the element so it can change appearance
|
||||
onPlay: function(){
|
||||
_V_.removeClass(this.el, "vjs-paused");
|
||||
_V_.addClass(this.el, "vjs-playing");
|
||||
},
|
||||
|
||||
// OnPause - Add the vjs-paused class to the element so it can change appearance
|
||||
onPause: function(){
|
||||
_V_.removeClass(this.el, "vjs-playing");
|
||||
_V_.addClass(this.el, "vjs-paused");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
/* Fullscreen Toggle Behaviors
|
||||
================================================================================ */
|
||||
_V_.FullscreenToggle = _V_.Button.extend({
|
||||
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
className: "vjs-fullscreen-control vjs-control",
|
||||
innerHTML: '<div><span class="vjs-control-text">Fullscreen</span></div>'
|
||||
});
|
||||
},
|
||||
|
||||
onClick: function(){
|
||||
if (!this.player.videoIsFullScreen) {
|
||||
this.player.enterFullScreen();
|
||||
} else {
|
||||
this.player.exitFullScreen();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Big Play Button
|
||||
================================================================================ */
|
||||
_V_.BigPlayButton = _V_.Button.extend({
|
||||
init: function(player){
|
||||
|
||||
player.addEvent("play", _V_.proxy(this, this.hide));
|
||||
player.addEvent("ended", _V_.proxy(this, this.show));
|
||||
|
||||
return this._super(player);
|
||||
},
|
||||
|
||||
createElement: function(){
|
||||
return this._super("div", {
|
||||
className: "vjs-big-play-button",
|
||||
innerHTML: "<span></span>"
|
||||
});
|
||||
},
|
||||
|
||||
onClick: function(){
|
||||
this.player.play();
|
||||
}
|
||||
});
|
||||
|
||||
/* Control Bar
|
||||
================================================================================ */
|
||||
_V_.ControlBar = _V_.UIComponent.extend({
|
||||
init: function(player, options){
|
||||
this.player = player;
|
||||
this.el = this.createElement();
|
||||
this.children = [];
|
||||
|
||||
player.addEvent("mouseover", _V_.proxy(this, this.show));
|
||||
player.addEvent("mouseout", _V_.proxy(this, this.hide));
|
||||
|
||||
_V_.each(options.children, function(child){
|
||||
this.appendUI(child)
|
||||
});
|
||||
|
||||
var pt = this.children["playToggle"] = new _V_.PlayToggle(player);
|
||||
this.el.appendChild(pt.el);
|
||||
|
||||
var ft = this.children["fullscreenToggle"] = new _V_.FullscreenToggle(player);
|
||||
this.el.appendChild(ft.el);
|
||||
},
|
||||
|
||||
createElement: function(){
|
||||
return _V_.createElement("div", {
|
||||
className: "vjs-controls"
|
||||
});
|
||||
},
|
||||
|
||||
appendUI: function(name){
|
||||
var component = this.children[name] = new _V_.PlayToggle(player);
|
||||
this.el.appendChild(pt.el);
|
||||
},
|
||||
|
||||
show: function(){
|
||||
// Used for transitions (fading out)
|
||||
this.el.style.opacity = 1;
|
||||
// bar.style.display = "block";
|
||||
},
|
||||
hide: function(){
|
||||
this.el.style.opacity = 0;
|
||||
// bar.style.display = "none";
|
||||
}
|
||||
});
|
Loading…
Reference in New Issue
Block a user