Initial commit

This commit is contained in:
vsphim
2026-05-20 11:41:11 +07:00
commit 364820653b
54 changed files with 6150 additions and 0 deletions

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

48
README.md Executable file
View File

@@ -0,0 +1,48 @@
# THEME - SEPTEMBER 2022 - VSPHIM CMS
## Demo
### Trang Chủ
![Alt text](https://i.ibb.co/bWkS4Sf/September-Home-Page.png "Home Page")
### Trang Danh Sách Phim
![Alt text](https://i.ibb.co/B2dPj5S/September-Catalog-Page.png "Catalog Page")
### Trang Thông Tin Phim
![Alt text](https://i.ibb.co/6r1Z70Y/September-Single-Page.png "Single Page")
### Trang Xem Phim
![Alt text](https://i.ibb.co/Pxb8m1G/September-Episode-Page.png "Episode Page")
## Requirements
https://github.com/vsphim/vsmov-core
## Install
1. Tại thư mục của Project: `composer require vsmov/vsmov-september`
2. Kích hoạt giao diện trong Admin Panel
## Update
1. Tại thư mục của Project: `composer update vsmov/vsmov-september`
2. Re-Activate giao diện trong Admin Panel
## Note
- Một vài lưu ý quan trọng của các nút chức năng:
+ `Activate` và `Re-Activate` sẽ publish toàn bộ file js,css trong themes ra ngoài public của laravel.
+ `Reset` reset lại toàn bộ cấu hình của themes
## Document
### List
- Trang chủ: `display_label|relation|find_by_field|value|limit|show_more_url|show_template (slider_poster|slider_thumb|section_poster|section_thumb)`
+ Ví dụ theo định dạng: `Phim bộ mới||type|series|12|/danh-sach/phim-bo|section_poster`
+ Ví dụ theo định dạng: `Phim lẻ mới||type|single|12|/danh-sach/phim-bo|section_thumb`
+ Ví dụ theo thể loại: `Phim hành động|categories|slug|hanh-dong|12|/the-loai/hanh-dong|slider_thumb`
+ Ví dụ theo quốc gia: `Phim hàn quốc|regions|slug|han-quoc|12|/quoc-gia/han-quoc|section_poster`
+ Ví dụ với các field khác: `Phim chiếu rạp||is_shown_in_theater|1|12||section_poster`
- Danh sách hot: `Label|relation|find_by_field|value|sort_by_field|sort_algo|limit|show_template (top_text|top_thumb)`
+ `Phim sắp chiếu||status|trailer|publish_year|desc|9|top_text`
+ `Top phim bộ||type|series|view_total|desc|9|top_thumb`
+ `Top phim lẻ||type|single|view_total|desc|9|top_thumb`
### Custom View Blade
- File blade gốc trong Package: `/vendor/vsmov/vsmov-september/resources/views/september`
- Copy file cần custom đến: `/resources/views/vendor/themes/september`

32
composer.json Executable file
View File

@@ -0,0 +1,32 @@
{
"name": "vsmov/vsmov-september",
"description": "VsMov's september theme",
"type": "library",
"authors": [
{
"name": "vsmov",
"email": "vsmov@gmail.com"
}
],
"require": {
"laravel/framework": "^6|^7|^8",
"ckfinder/ckfinder-laravel-package": "v3.5.2.1",
"vsmov/vsmov-core": "^1.0.1"
},
"license": "MIT",
"autoload": {
"psr-4": {
"VsMov\\September\\": "src/",
"VsMov\\September\\Database\\Factories\\": "database/factories/",
"VsMov\\September\\Database\\Seeders\\": "database/seeders/"
}
},
"extra": {
"laravel": {
"providers": [
"Vsmov\\September\\SeptemberServiceProvider"
]
}
},
"minimum-stability": "stable"
}

File diff suppressed because it is too large Load Diff

1
resources/assets/css/styles.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="raty" horiz-adv-x="512">
<font-face units-per-em="512" ascent="480" descent="-32" />
<missing-glyph horiz-adv-x="512" />
<glyph unicode="&#x20;" d="" horiz-adv-x="256" />
<glyph unicode="&#xe600;" d="M256 16c-114.88 0-208 93.12-208 208s93.12 208 208 208 208-93.12 208-208-93.12-208-208-208zM351.376 284.656c3.904 3.904 3.904 10.256 0 14.16l-21.248 21.232c-3.904 3.904-10.256 3.904-14.16 0l-60.176-60.176-60.176 60.176c-3.904 3.904-10.256 3.904-14.16 0l-21.248-21.232c-3.904-3.904-3.904-10.256 0-14.16l60.192-60.192-60.192-60.16c-3.904-3.904-3.904-10.256 0-14.16l21.248-21.248c3.904-3.904 10.256-3.904 14.16 0l60.176 60.192 60.176-60.192c3.904-3.904 10.256-3.904 14.16 0l21.248 21.248c3.904 3.904 3.904 10.256 0 14.16l-60.192 60.16 60.192 60.192z" />
<glyph unicode="&#xe601;" d="M256 16c-114.88 0-208 93.12-208 208s93.12 208 208 208 208-93.12 208-208-93.12-208-208-208zM256 384c-88.352 0-160-71.648-160-160s71.648-160 160-160c88.368 0 160 71.648 160 160s-71.632 160-160 160zM328.592 167.44l-16.224-16.224c-2.976-2.976-7.808-2.976-10.8 0l-45.92 45.92-45.92-45.92c-2.992-2.976-7.808-2.976-10.8 0l-16.224 16.224c-2.976 2.976-2.976 7.808 0 10.8l45.936 45.904-45.936 45.92c-2.976 2.992-2.976 7.824 0 10.816l16.224 16.208c2.992 2.992 7.808 2.992 10.8 0l45.92-45.92 45.92 45.92c2.992 2.992 7.824 2.992 10.8 0l16.224-16.208c2.976-2.992 2.976-7.824 0-10.816l-45.936-45.92 45.936-45.904c2.976-2.992 2.976-7.84 0-10.8z" />
<glyph unicode="&#xf005;" d="M475.428 290.572q0-6.286-7.428-13.714l-103.714-101.143 24.572-142.857q0.286-2 0.286-5.714 0-6-3-10.143t-8.714-4.143q-5.428 0-11.428 3.428l-128.286 67.428-128.286-67.428q-6.285-3.428-11.428-3.428-6 0-9 4.143t-3 10.143q0 1.714 0.572 5.714l24.572 142.857-104 101.143q-7.143 7.714-7.143 13.714 0 10.572 16 13.143l143.428 20.857 64.286 130q5.428 11.714 14 11.714t14-11.714l64.286-130 143.429-20.857q16-2.572 16-13.143z" horiz-adv-x="476" />
<glyph unicode="&#xf006;" d="M324.857 188.572l87.428 84.857-120.572 17.715-54 109.143-54-109.143-120.572-17.714 87.428-84.857-20.857-120.286 108 56.857 107.714-56.857zM475.428 290.572q0-6.286-7.428-13.714l-103.714-101.143 24.572-142.857q0.286-2 0.286-5.714 0-14.286-11.714-14.286-5.428 0-11.428 3.428l-128.286 67.428-128.286-67.428q-6.285-3.428-11.428-3.428-6 0-9 4.143t-3 10.143q0 1.714 0.572 5.714l24.572 142.857-104 101.143q-7.143 7.714-7.143 13.714 0 10.572 16 13.143l143.428 20.857 64.286 130q5.428 11.714 14 11.714t14-11.714l64.286-130 143.429-20.857q16-2.572 16-13.143z" horiz-adv-x="476" />
<glyph unicode="&#xf123;" d="M338.857 202l73.428 71.428-120.572 17.714-8.572 17.143-45.428 92v-275.143l16.857-8.857 90.857-48-17.143 101.428-3.428 18.857zM468 276.857l-103.714-101.143 24.572-142.857q1.428-9.428-1.714-14.714t-9.714-5.286q-4.857 0-11.428 3.428l-128.286 67.428-128.286-67.428q-6.572-3.428-11.428-3.428-6.572 0-9.715 5.286t-1.715 14.714l24.572 142.857-104 101.143q-9.143 9.143-6.572 17t15.428 9.857l143.429 20.857 64.286 130q5.714 11.714 14 11.714 8 0 14-11.714l64.286-130 143.429-20.857q12.857-2 15.428-9.857t-6.857-17z" horiz-adv-x="476" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 958 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,46 @@
.cancel-on-png, .cancel-off-png, .star-on-png, .star-off-png, .star-half-png {
font-size: 2em;
}
@font-face {
font-family: "raty";
font-style: normal;
font-weight: normal;
src: url("./fonts/raty.eot");
src: url("./fonts/raty.eot?#iefix") format("embedded-opentype");
src: url("./fonts/raty.svg#raty") format("svg");
src: url("./fonts/raty.ttf") format("truetype");
src: url("./fonts/raty.woff") format("woff");
}
.cancel-on-png, .cancel-off-png, .star-on-png, .star-off-png, .star-half-png {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-family: "raty";
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1;
speak: none;
text-transform: none;
}
.cancel-on-png:before {
content: "\e600";
}
.cancel-off-png:before {
content: "\e601";
}
.star-on-png:before {
content: "\f005";
}
.star-off-png:before {
content: "\f006";
}
.star-half-png:before {
content: "\f123";
}

View File

@@ -0,0 +1,777 @@
/*!
* Raty - A Star Rating Plugin
*
* The MIT License
*
* author: Washington Botelho
* github: wbotelhos/raty
* version: 3.1.0
*
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory(require('jquery'));
} else {
factory(root.jQuery);
}
}(this, function($) {
'use strict';
$.raty = {
cancelButton: false,
cancelClass: 'raty-cancel',
cancelHint: 'Cancel this rating!',
cancelOff: 'cancel-off.png',
cancelOn: 'cancel-on.png',
cancelPlace: 'left',
click: undefined,
half: false,
halfShow: true,
hints: ['bad', 'poor', 'regular', 'good', 'gorgeous'],
iconRange: undefined,
iconRangeSame: false,
mouseout: undefined,
mouseover: undefined,
noRatedMsg: 'Not rated yet!',
number: 5,
numberMax: 20,
path: undefined,
precision: false,
readOnly: false,
round: { down: 0.25, full: 0.6, up: 0.76 },
score: undefined,
scoreName: 'score',
single: false,
space: true,
starHalf: 'star-half.png',
starOff: 'star-off.png',
starOn: 'star-on.png',
starType: 'img',
target: undefined,
targetFormat: '{score}',
targetKeep: false,
targetScore: undefined,
targetText: '',
targetType: 'hint'
};
$.fn.raty = function(options) {
return this.each(function() {
var instance = new $.raty.Raty(this, options);
return instance._create();
});
};
$.raty.Raty = (function() {
var Raty = function(element, options) {
this.element = element;
this.self = $(element);
this.opt = $.extend(true, {}, $.raty, options, this.self.data());
};
Raty.prototype = {
_create: function() {
this._executeCallbacks();
this._adjustNumber();
this._adjustHints();
this.opt.score = this._adjustedScore(this.opt.score);
if (this.opt.starType !== 'img') {
this._adjustStarName();
}
this._setPath();
this._createStars();
if (this.opt.cancelButton) {
this._createCancel();
}
if (this.opt.precision) {
this._adjustPrecision();
}
this._createScore();
this._apply(this.opt.score);
this._setTitle(this.opt.score);
this._target(this.opt.score);
if (this.opt.readOnly) {
this._lock();
} else {
this.element.style.cursor = 'pointer';
this._binds();
}
this.self.data('raty', this);
},
// TODO: model spec
_adjustedScore: function(score) {
if (score || score === 0) {
return this._between(score, 0, this.opt.number);
}
},
_adjustHints: function() {
if (!this.opt.hints) {
this.opt.hints = [];
}
if (!this.opt.halfShow && !this.opt.half) {
return;
}
var steps = this.opt.precision ? 10 : 2;
for (var i = 0; i < this.opt.number; i++) {
var group = this.opt.hints[i];
if (Object.prototype.toString.call(group) !== '[object Array]') {
group = [group];
}
this.opt.hints[i] = [];
for (var j = 0; j < steps; j++) {
var hint = group[j];
var last = group[group.length - 1];
if (last === undefined) {
last = null;
}
this.opt.hints[i][j] = hint === undefined ? last : hint;
}
}
},
_adjustNumber: function() {
this.opt.number = this._between(this.opt.number, 1, this.opt.numberMax);
},
_adjustPrecision: function() {
this.opt.half = true;
},
_adjustStarName: function() {
var replaces = ['cancelOff', 'cancelOn', 'starHalf', 'starOff', 'starOn'];
this.opt.path = '';
for (var i = 0; i < replaces.length; i++) {
this.opt[replaces[i]] = this.opt[replaces[i]].replace('.', '-');
}
},
// TODO: model spec
_apply: function(score) {
this._fill(score);
if (score) {
if (score > 0) {
this.scoreField.val(score);
}
this._roundStars(score);
}
},
_attributesForIndex: function(i) {
var name = this._nameForIndex(i);
var attributes = { alt: i, src: this.opt.path + this.opt[name] };
if (this.opt.starType !== 'img') {
attributes = { 'data-alt': i, 'class': this.opt[name] };
}
attributes.title = this._getHint(i);
return attributes;
},
_between: function(value, min, max) {
return Math.min(Math.max(parseFloat(value), min), max);
},
// TODO: model spec
_binds: function() {
if (this.cancelButton) {
this._bindOverCancel();
this._bindClickCancel();
this._bindOutCancel();
}
this._bindOver();
this._bindClick();
this._bindOut();
},
// TODO: model spec
_bindClick: function() {
var that = this;
this.stars.on('click.raty', function(evt) {
if (that.self.data('readonly')) {
return;
}
var execute = true;
var score = (that.opt.half || that.opt.precision) ? that.self.data('score') : (this.alt || $(this).data('alt'));
if (that.opt.half && !that.opt.precision) {
score = that._roundHalfScore(score);
}
if (that.opt.click) {
execute = that.opt.click.call(that.element, +score, evt);
}
if (execute || execute === undefined) {
that._apply(+score);
}
});
},
// TODO: model spec
_bindClickCancel: function() {
this.cancelButton.on('click.raty', function(evt) {
this.scoreField.removeAttr('value');
if (this.opt.click) {
this.opt.click.call(this.element, null, evt);
}
}.bind(this));
},
// TODO: model spec
_bindOut: function() {
this.self.on('mouseleave.raty', function(evt) {
var score = +this.scoreField.val() || undefined;
this._apply(score);
this._target(score, evt);
this._resetTitle();
if (this.opt.mouseout) {
this.opt.mouseout.call(this.element, score, evt);
}
}.bind(this));
},
// TODO: model spec
_bindOutCancel: function() {
var that = this;
this.cancelButton.on('mouseleave.raty', function(evt) {
var icon = that.opt.cancelOff;
if (that.opt.starType !== 'img') {
icon = that.opt.cancelClass + ' ' + icon;
}
that._setIcon(this, icon);
if (that.opt.mouseout) {
var score = +that.scoreField.val() || undefined;
that.opt.mouseout.call(that.element, score, evt);
}
});
},
// TODO: model spec
_bindOver: function() {
var that = this;
var action = that.opt.half ? 'mousemove.raty' : 'mouseover.raty';
this.stars.on(action, function(evt) {
var score = that._getScoreByPosition(evt, this);
that._fill(score);
if (that.opt.half) {
that._roundStars(score, evt);
that._setTitle(score, evt);
that.self.data('score', score);
}
that._target(score, evt);
if (that.opt.mouseover) {
that.opt.mouseover.call(that.element, score, evt);
}
});
},
// TODO: model spec
_bindOverCancel: function() {
var that = this;
this.cancelButton.on('mouseover.raty', function(evt) {
if (that.self.data('readonly')) {
return;
}
var starOff = that.opt.path + that.opt.starOff;
var icon = that.opt.cancelOn;
if (that.opt.starType === 'img') {
that.stars.attr('src', starOff);
} else {
icon = that.opt.cancelClass + ' ' + icon;
that.stars.attr('class', starOff);
}
that._setIcon(this, icon);
that._target(null, evt);
if (that.opt.mouseover) {
that.opt.mouseover.call(that.element, null);
}
});
},
// TODO: model spec
_buildScoreField: function() {
return $('<input />', { name: this.opt.scoreName, type: 'hidden' }).appendTo(this.self);
},
// TODO: model spec
_createCancel: function() {
var icon = this.opt.path + this.opt.cancelOff;
var button = $('<' + this.opt.starType + ' />', { title: this.opt.cancelHint, 'class': this.opt.cancelClass });
if (this.opt.starType === 'img') {
button.attr({ src: icon, alt: 'x' });
} else {
// TODO: use $.data
button.attr('data-alt', 'x').addClass(icon);
}
if (this.opt.cancelPlace === 'left') {
this.self.prepend('&#160;').prepend(button);
} else {
this.self.append('&#160;').append(button);
}
this.cancelButton = button;
},
// TODO: model spec
_createScore: function() {
var score = $(this.opt.targetScore);
this.scoreField = score.length ? score : this._buildScoreField();
},
_createStars: function() {
for (var i = 1; i <= this.opt.number; i++) {
var attributes = this._attributesForIndex(i);
$('<' + this.opt.starType + ' />', attributes).appendTo(this.element);
if (this.opt.space && i < this.opt.number) {
this.self.append('&#160;');
}
}
this.stars = this.self.children(this.opt.starType);
},
// TODO: model spec
_error: function(message) {
$(this).text(message);
$.error(message);
},
_executeCallbacks: function() {
var options = ['number', 'readOnly', 'score', 'scoreName', 'target', 'path'];
for (var i = 0; i < options.length; i++) {
if (typeof this.opt[options[i]] === 'function') {
var value = this.opt[options[i]].call(this.element);
if (value) {
this.opt[options[i]] = value;
} else {
delete this.opt[options[i]];
}
}
}
},
// TODO: model spec
_fill: function(score) {
var hash = 0;
if (this.opt.iconRangeSame && this.opt.iconRange) {
while (hash < this.opt.iconRange.length && this.opt.iconRange[hash].range < score) {
hash++;
}
}
for (var i = 1; i <= this.stars.length; i++) {
var icon;
var star = this.stars[i - 1];
var turnOn = this._turnOn(i, score);
if (this.opt.iconRange && this.opt.iconRange.length > hash) {
var irange = this.opt.iconRange[hash];
icon = this._getRangeIcon(irange, turnOn);
if (i <= irange.range) {
this._setIcon(star, icon);
}
if (i === irange.range) {
hash++;
}
} else {
icon = this.opt[turnOn ? 'starOn' : 'starOff'];
this._setIcon(star, icon);
}
}
},
_getDecimal: function(number, fractions) {
var decimal = number.toString().split('.')[1];
var result = 0;
if (decimal) {
result = parseInt(decimal.slice(0, fractions), 10);
if (decimal.slice(1, 5) === '9999') {
result++;
}
}
return result;
},
// TODO: model spec
_getRangeIcon: function(irange, turnOn) {
return turnOn ? irange.on || this.opt.starOn : irange.off || this.opt.starOff;
},
// TODO: model spec
_getScoreByPosition: function(evt, icon) {
var score = parseInt(icon.alt || icon.getAttribute('data-alt'), 10);
if (this.opt.half) {
var size = this._getWidth();
var percent = parseFloat((evt.pageX - $(icon).offset().left) / size);
score = score - 1 + percent;
}
return score;
},
// TODO: model spec
_getHint: function(score, evt) {
if (score !== 0 && !score) {
return this.opt.noRatedMsg;
}
var decimal = this._getDecimal(score, 1);
var integer = Math.ceil(score);
var group = this.opt.hints[(integer || 1) - 1];
var hint = group;
var set = !evt || this.isMove;
if (this.opt.precision) {
if (set) {
decimal = decimal === 0 ? 9 : decimal - 1;
}
hint = group[decimal];
} else if (this.opt.halfShow || this.opt.half) {
decimal = set && decimal === 0 ? 1 : decimal > 5 ? 1 : 0;
hint = group[decimal];
}
return hint === '' ? '' : hint || score;
},
// TODO: model spec
_getWidth: function() {
var width = this.stars[0].width || parseFloat(this.stars.eq(0).css('font-size'));
if (!width) {
this._error('Could not get the icon width!');
}
return width;
},
// TODO: model spec
_lock: function() {
var hint = this._getHint(this.scoreField.val());
this.element.style.cursor = '';
this.element.title = hint;
this.scoreField.prop('readonly', true);
this.stars.prop('title', hint);
if (this.cancelButton) {
this.cancelButton.hide();
}
this.self.data('readonly', true);
},
_nameForIndex: function(i) {
return this.opt.score && this.opt.score >= i ? 'starOn' : 'starOff';
},
// TODO: model spec
_resetTitle: function() {
for (var i = 0; i < this.opt.number; i++) {
this.stars[i].title = this._getHint(i + 1);
}
},
// TODO: model spec
_roundHalfScore: function(score) {
var integer = parseInt(score, 10);
var decimal = this._getDecimal(score, 1);
if (decimal !== 0) {
decimal = decimal > 5 ? 1 : 0.5;
}
return integer + decimal;
},
// TODO: model spec
_roundStars: function(score, evt) {
var name = this._starName(score, evt);
if (name) {
var icon = this.opt[name];
var star = this.stars[Math.ceil(score) - 1];
this._setIcon(star, icon);
} // Full down: [x.00 .. x.25]
},
// TODO: model spec
_setIcon: function(star, icon) {
star[this.opt.starType === 'img' ? 'src' : 'className'] = this.opt.path + icon;
},
_setPath: function() {
this.opt.path = this.opt.path || '';
if (this.opt.path && this.opt.path.slice(-1)[0] !== '/') {
this.opt.path += '/';
}
},
// TODO: model spec
_setTarget: function(target, score) {
if (score) {
score = this.opt.targetFormat.toString().replace('{score}', score);
}
if (target.is(':input')) {
target.val(score);
} else {
target.html(score);
}
},
// TODO: model spec
_setTitle: function(score, evt) {
if (score) {
var integer = parseInt(Math.ceil(score), 10);
var star = this.stars[integer - 1];
star.title = this._getHint(score, evt);
}
},
_starName: function(score, evt) {
var decimal = +(score % 1).toFixed(2);
if (evt || this.isMove) {
return decimal > 0.5 ? 'starOn' : 'starHalf';
}
if (decimal <= this.opt.round.down) { // Down: [x.00 ... x.25]
return;
}
if (this.opt.halfShow && decimal < this.opt.round.up) { // Half: [x.26 ... x.75]
return 'starHalf';
}
if (decimal < this.opt.round.full) { // Off: [x.26 .. x.6]
return 'starOff';
}
return 'starOn'; // Up: [x.26 ...] || [x.6 ...]
},
// TODO: model spec
_target: function(score, evt) {
if (this.opt.target) {
var target = $(this.opt.target);
if (!target.length) {
this._error('Target selector invalid or missing!');
}
var mouseover = evt && evt.type === 'mouseover';
if (score === undefined) {
score = this.opt.targetText;
} else if (score === null) {
score = mouseover ? this.opt.cancelHint : this.opt.targetText;
} else {
if (this.opt.targetType === 'hint') {
score = this._getHint(score, evt);
} else if (this.opt.precision) {
score = parseFloat(score).toFixed(1);
}
var mousemove = evt && evt.type === 'mousemove';
if (!mouseover && !mousemove && !this.opt.targetKeep) {
score = this.opt.targetText;
}
}
this._setTarget(target, score);
}
},
// TODO: model spec
_turnOn: function(i, score) {
return this.opt.single ? (i === score) : (i <= score);
},
// TODO: model spec
_unlock: function() {
this.element.style.cursor = 'pointer';
this.element.removeAttribute('title');
this.scoreField.removeAttr('readonly');
this.self.data('readonly', false);
this._resetTitle();
if (this.cancelButton) {
this.cancelButton.css('display', '');
}
},
// TODO: model spec
cancel: function(click) {
if (this.self.data('readonly') !== true) {
this[click ? 'click' : 'score'].call(this, null);
this.scoreField.removeAttr('value');
}
},
// TODO: model spec
click: function(score) {
if (this.self.data('readonly') !== true) {
score = this._adjustedScore(score);
this._apply(score);
if (this.opt.click) {
this.opt.click.call(this.element, score, $.Event('click'));
}
this._target(score);
}
},
// TODO: model spec
getScore: function() {
var score = [];
var value ;
value = this.scoreField.val();
score.push(value ? +value : undefined);
return (score.length > 1) ? score : score[0];
},
// TODO: model spec
move: function(score) {
var integer = parseInt(score, 10);
var decimal = this._getDecimal(score, 1);
if (integer >= this.opt.number) {
integer = this.opt.number - 1;
decimal = 10;
}
var width = this._getWidth();
var steps = width / 10;
var star = $(this.stars[integer]);
var percent = star.offset().left + steps * decimal;
var evt = $.Event('mousemove', { pageX: percent });
this.isMove = true;
star.trigger(evt);
this.isMove = false;
},
// TODO: model spec
readOnly: function(readonly) {
if (this.self.data('readonly') !== readonly) {
if (readonly) {
this.self.off('.raty').children(this.opt.starType).off('.raty');
this._lock();
} else {
this._binds();
this._unlock();
}
this.self.data('readonly', readonly);
}
},
// TODO: model spec
score: function() {
return arguments.length ? this.setScore.apply(this, arguments) : this.getScore();
},
setScore: function(score) {
if (this.self.data('readonly') !== true) {
score = this._adjustedScore(score);
this._apply(score);
this._target(score);
}
}
};
return Raty;
})();
}));

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="raty" horiz-adv-x="512">
<font-face units-per-em="512" ascent="480" descent="-32" />
<missing-glyph horiz-adv-x="512" />
<glyph unicode="&#x20;" d="" horiz-adv-x="256" />
<glyph unicode="&#xe600;" d="M256 16c-114.88 0-208 93.12-208 208s93.12 208 208 208 208-93.12 208-208-93.12-208-208-208zM351.376 284.656c3.904 3.904 3.904 10.256 0 14.16l-21.248 21.232c-3.904 3.904-10.256 3.904-14.16 0l-60.176-60.176-60.176 60.176c-3.904 3.904-10.256 3.904-14.16 0l-21.248-21.232c-3.904-3.904-3.904-10.256 0-14.16l60.192-60.192-60.192-60.16c-3.904-3.904-3.904-10.256 0-14.16l21.248-21.248c3.904-3.904 10.256-3.904 14.16 0l60.176 60.192 60.176-60.192c3.904-3.904 10.256-3.904 14.16 0l21.248 21.248c3.904 3.904 3.904 10.256 0 14.16l-60.192 60.16 60.192 60.192z" />
<glyph unicode="&#xe601;" d="M256 16c-114.88 0-208 93.12-208 208s93.12 208 208 208 208-93.12 208-208-93.12-208-208-208zM256 384c-88.352 0-160-71.648-160-160s71.648-160 160-160c88.368 0 160 71.648 160 160s-71.632 160-160 160zM328.592 167.44l-16.224-16.224c-2.976-2.976-7.808-2.976-10.8 0l-45.92 45.92-45.92-45.92c-2.992-2.976-7.808-2.976-10.8 0l-16.224 16.224c-2.976 2.976-2.976 7.808 0 10.8l45.936 45.904-45.936 45.92c-2.976 2.992-2.976 7.824 0 10.816l16.224 16.208c2.992 2.992 7.808 2.992 10.8 0l45.92-45.92 45.92 45.92c2.992 2.992 7.824 2.992 10.8 0l16.224-16.208c2.976-2.992 2.976-7.824 0-10.816l-45.936-45.92 45.936-45.904c2.976-2.992 2.976-7.84 0-10.8z" />
<glyph unicode="&#xf005;" d="M475.428 290.572q0-6.286-7.428-13.714l-103.714-101.143 24.572-142.857q0.286-2 0.286-5.714 0-6-3-10.143t-8.714-4.143q-5.428 0-11.428 3.428l-128.286 67.428-128.286-67.428q-6.285-3.428-11.428-3.428-6 0-9 4.143t-3 10.143q0 1.714 0.572 5.714l24.572 142.857-104 101.143q-7.143 7.714-7.143 13.714 0 10.572 16 13.143l143.428 20.857 64.286 130q5.428 11.714 14 11.714t14-11.714l64.286-130 143.429-20.857q16-2.572 16-13.143z" horiz-adv-x="476" />
<glyph unicode="&#xf006;" d="M324.857 188.572l87.428 84.857-120.572 17.715-54 109.143-54-109.143-120.572-17.714 87.428-84.857-20.857-120.286 108 56.857 107.714-56.857zM475.428 290.572q0-6.286-7.428-13.714l-103.714-101.143 24.572-142.857q0.286-2 0.286-5.714 0-14.286-11.714-14.286-5.428 0-11.428 3.428l-128.286 67.428-128.286-67.428q-6.285-3.428-11.428-3.428-6 0-9 4.143t-3 10.143q0 1.714 0.572 5.714l24.572 142.857-104 101.143q-7.143 7.714-7.143 13.714 0 10.572 16 13.143l143.428 20.857 64.286 130q5.428 11.714 14 11.714t14-11.714l64.286-130 143.429-20.857q16-2.572 16-13.143z" horiz-adv-x="476" />
<glyph unicode="&#xf123;" d="M338.857 202l73.428 71.428-120.572 17.714-8.572 17.143-45.428 92v-275.143l16.857-8.857 90.857-48-17.143 101.428-3.428 18.857zM468 276.857l-103.714-101.143 24.572-142.857q1.428-9.428-1.714-14.714t-9.714-5.286q-4.857 0-11.428 3.428l-128.286 67.428-128.286-67.428q-6.572-3.428-11.428-3.428-6.572 0-9.715 5.286t-1.715 14.714l24.572 142.857-104 101.143q-9.143 9.143-6.572 17t15.428 9.857l143.429 20.857 64.286 130q5.714 11.714 14 11.714 8 0 14-11.714l64.286-130 143.429-20.857q12.857-2 15.428-9.857t-6.857-17z" horiz-adv-x="476" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 781 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,46 @@
.cancel-on-png, .cancel-off-png, .star-on-png, .star-off-png, .star-half-png {
font-size: 2em;
}
@font-face {
font-family: "raty";
font-style: normal;
font-weight: normal;
src: url("./fonts/raty.eot");
src: url("./fonts/raty.eot?#iefix") format("embedded-opentype");
src: url("./fonts/raty.svg#raty") format("svg");
src: url("./fonts/raty.ttf") format("truetype");
src: url("./fonts/raty.woff") format("woff");
}
.cancel-on-png, .cancel-off-png, .star-on-png, .star-off-png, .star-half-png {
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
font-family: "raty";
font-style: normal;
font-variant: normal;
font-weight: normal;
line-height: 1;
speak: none;
text-transform: none;
}
.cancel-on-png:before {
content: "\e600";
}
.cancel-off-png:before {
content: "\e601";
}
.star-on-png:before {
content: "\f005";
}
.star-off-png:before {
content: "\f006";
}
.star-half-png:before {
content: "\f123";
}

View File

@@ -0,0 +1,777 @@
/*!
* Raty - A Star Rating Plugin
*
* The MIT License
*
* author: Washington Botelho
* github: wbotelhos/raty
* version: 3.1.0
*
*/
(function(root, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof module === 'object' && module.exports) {
module.exports = factory(require('jquery'));
} else {
factory(root.jQuery);
}
}(this, function($) {
'use strict';
$.raty = {
cancelButton: false,
cancelClass: 'raty-cancel',
cancelHint: 'Cancel this rating!',
cancelOff: 'cancel-off.png',
cancelOn: 'cancel-on.png',
cancelPlace: 'left',
click: undefined,
half: false,
halfShow: true,
hints: ['bad', 'poor', 'regular', 'good', 'gorgeous'],
iconRange: undefined,
iconRangeSame: false,
mouseout: undefined,
mouseover: undefined,
noRatedMsg: 'Not rated yet!',
number: 5,
numberMax: 20,
path: undefined,
precision: false,
readOnly: false,
round: { down: 0.25, full: 0.6, up: 0.76 },
score: undefined,
scoreName: 'score',
single: false,
space: true,
starHalf: 'star-half.png',
starOff: 'star-off.png',
starOn: 'star-on.png',
starType: 'img',
target: undefined,
targetFormat: '{score}',
targetKeep: false,
targetScore: undefined,
targetText: '',
targetType: 'hint'
};
$.fn.raty = function(options) {
return this.each(function() {
var instance = new $.raty.Raty(this, options);
return instance._create();
});
};
$.raty.Raty = (function() {
var Raty = function(element, options) {
this.element = element;
this.self = $(element);
this.opt = $.extend(true, {}, $.raty, options, this.self.data());
};
Raty.prototype = {
_create: function() {
this._executeCallbacks();
this._adjustNumber();
this._adjustHints();
this.opt.score = this._adjustedScore(this.opt.score);
if (this.opt.starType !== 'img') {
this._adjustStarName();
}
this._setPath();
this._createStars();
if (this.opt.cancelButton) {
this._createCancel();
}
if (this.opt.precision) {
this._adjustPrecision();
}
this._createScore();
this._apply(this.opt.score);
this._setTitle(this.opt.score);
this._target(this.opt.score);
if (this.opt.readOnly) {
this._lock();
} else {
this.element.style.cursor = 'pointer';
this._binds();
}
this.self.data('raty', this);
},
// TODO: model spec
_adjustedScore: function(score) {
if (score || score === 0) {
return this._between(score, 0, this.opt.number);
}
},
_adjustHints: function() {
if (!this.opt.hints) {
this.opt.hints = [];
}
if (!this.opt.halfShow && !this.opt.half) {
return;
}
var steps = this.opt.precision ? 10 : 2;
for (var i = 0; i < this.opt.number; i++) {
var group = this.opt.hints[i];
if (Object.prototype.toString.call(group) !== '[object Array]') {
group = [group];
}
this.opt.hints[i] = [];
for (var j = 0; j < steps; j++) {
var hint = group[j];
var last = group[group.length - 1];
if (last === undefined) {
last = null;
}
this.opt.hints[i][j] = hint === undefined ? last : hint;
}
}
},
_adjustNumber: function() {
this.opt.number = this._between(this.opt.number, 1, this.opt.numberMax);
},
_adjustPrecision: function() {
this.opt.half = true;
},
_adjustStarName: function() {
var replaces = ['cancelOff', 'cancelOn', 'starHalf', 'starOff', 'starOn'];
this.opt.path = '';
for (var i = 0; i < replaces.length; i++) {
this.opt[replaces[i]] = this.opt[replaces[i]].replace('.', '-');
}
},
// TODO: model spec
_apply: function(score) {
this._fill(score);
if (score) {
if (score > 0) {
this.scoreField.val(score);
}
this._roundStars(score);
}
},
_attributesForIndex: function(i) {
var name = this._nameForIndex(i);
var attributes = { alt: i, src: this.opt.path + this.opt[name] };
if (this.opt.starType !== 'img') {
attributes = { 'data-alt': i, 'class': this.opt[name] };
}
attributes.title = this._getHint(i);
return attributes;
},
_between: function(value, min, max) {
return Math.min(Math.max(parseFloat(value), min), max);
},
// TODO: model spec
_binds: function() {
if (this.cancelButton) {
this._bindOverCancel();
this._bindClickCancel();
this._bindOutCancel();
}
this._bindOver();
this._bindClick();
this._bindOut();
},
// TODO: model spec
_bindClick: function() {
var that = this;
this.stars.on('click.raty', function(evt) {
if (that.self.data('readonly')) {
return;
}
var execute = true;
var score = (that.opt.half || that.opt.precision) ? that.self.data('score') : (this.alt || $(this).data('alt'));
if (that.opt.half && !that.opt.precision) {
score = that._roundHalfScore(score);
}
if (that.opt.click) {
execute = that.opt.click.call(that.element, +score, evt);
}
if (execute || execute === undefined) {
that._apply(+score);
}
});
},
// TODO: model spec
_bindClickCancel: function() {
this.cancelButton.on('click.raty', function(evt) {
this.scoreField.removeAttr('value');
if (this.opt.click) {
this.opt.click.call(this.element, null, evt);
}
}.bind(this));
},
// TODO: model spec
_bindOut: function() {
this.self.on('mouseleave.raty', function(evt) {
var score = +this.scoreField.val() || undefined;
this._apply(score);
this._target(score, evt);
this._resetTitle();
if (this.opt.mouseout) {
this.opt.mouseout.call(this.element, score, evt);
}
}.bind(this));
},
// TODO: model spec
_bindOutCancel: function() {
var that = this;
this.cancelButton.on('mouseleave.raty', function(evt) {
var icon = that.opt.cancelOff;
if (that.opt.starType !== 'img') {
icon = that.opt.cancelClass + ' ' + icon;
}
that._setIcon(this, icon);
if (that.opt.mouseout) {
var score = +that.scoreField.val() || undefined;
that.opt.mouseout.call(that.element, score, evt);
}
});
},
// TODO: model spec
_bindOver: function() {
var that = this;
var action = that.opt.half ? 'mousemove.raty' : 'mouseover.raty';
this.stars.on(action, function(evt) {
var score = that._getScoreByPosition(evt, this);
that._fill(score);
if (that.opt.half) {
that._roundStars(score, evt);
that._setTitle(score, evt);
that.self.data('score', score);
}
that._target(score, evt);
if (that.opt.mouseover) {
that.opt.mouseover.call(that.element, score, evt);
}
});
},
// TODO: model spec
_bindOverCancel: function() {
var that = this;
this.cancelButton.on('mouseover.raty', function(evt) {
if (that.self.data('readonly')) {
return;
}
var starOff = that.opt.path + that.opt.starOff;
var icon = that.opt.cancelOn;
if (that.opt.starType === 'img') {
that.stars.attr('src', starOff);
} else {
icon = that.opt.cancelClass + ' ' + icon;
that.stars.attr('class', starOff);
}
that._setIcon(this, icon);
that._target(null, evt);
if (that.opt.mouseover) {
that.opt.mouseover.call(that.element, null);
}
});
},
// TODO: model spec
_buildScoreField: function() {
return $('<input />', { name: this.opt.scoreName, type: 'hidden' }).appendTo(this.self);
},
// TODO: model spec
_createCancel: function() {
var icon = this.opt.path + this.opt.cancelOff;
var button = $('<' + this.opt.starType + ' />', { title: this.opt.cancelHint, 'class': this.opt.cancelClass });
if (this.opt.starType === 'img') {
button.attr({ src: icon, alt: 'x' });
} else {
// TODO: use $.data
button.attr('data-alt', 'x').addClass(icon);
}
if (this.opt.cancelPlace === 'left') {
this.self.prepend('&#160;').prepend(button);
} else {
this.self.append('&#160;').append(button);
}
this.cancelButton = button;
},
// TODO: model spec
_createScore: function() {
var score = $(this.opt.targetScore);
this.scoreField = score.length ? score : this._buildScoreField();
},
_createStars: function() {
for (var i = 1; i <= this.opt.number; i++) {
var attributes = this._attributesForIndex(i);
$('<' + this.opt.starType + ' />', attributes).appendTo(this.element);
if (this.opt.space && i < this.opt.number) {
this.self.append('&#160;');
}
}
this.stars = this.self.children(this.opt.starType);
},
// TODO: model spec
_error: function(message) {
$(this).text(message);
$.error(message);
},
_executeCallbacks: function() {
var options = ['number', 'readOnly', 'score', 'scoreName', 'target', 'path'];
for (var i = 0; i < options.length; i++) {
if (typeof this.opt[options[i]] === 'function') {
var value = this.opt[options[i]].call(this.element);
if (value) {
this.opt[options[i]] = value;
} else {
delete this.opt[options[i]];
}
}
}
},
// TODO: model spec
_fill: function(score) {
var hash = 0;
if (this.opt.iconRangeSame && this.opt.iconRange) {
while (hash < this.opt.iconRange.length && this.opt.iconRange[hash].range < score) {
hash++;
}
}
for (var i = 1; i <= this.stars.length; i++) {
var icon;
var star = this.stars[i - 1];
var turnOn = this._turnOn(i, score);
if (this.opt.iconRange && this.opt.iconRange.length > hash) {
var irange = this.opt.iconRange[hash];
icon = this._getRangeIcon(irange, turnOn);
if (i <= irange.range) {
this._setIcon(star, icon);
}
if (i === irange.range) {
hash++;
}
} else {
icon = this.opt[turnOn ? 'starOn' : 'starOff'];
this._setIcon(star, icon);
}
}
},
_getDecimal: function(number, fractions) {
var decimal = number.toString().split('.')[1];
var result = 0;
if (decimal) {
result = parseInt(decimal.slice(0, fractions), 10);
if (decimal.slice(1, 5) === '9999') {
result++;
}
}
return result;
},
// TODO: model spec
_getRangeIcon: function(irange, turnOn) {
return turnOn ? irange.on || this.opt.starOn : irange.off || this.opt.starOff;
},
// TODO: model spec
_getScoreByPosition: function(evt, icon) {
var score = parseInt(icon.alt || icon.getAttribute('data-alt'), 10);
if (this.opt.half) {
var size = this._getWidth();
var percent = parseFloat((evt.pageX - $(icon).offset().left) / size);
score = score - 1 + percent;
}
return score;
},
// TODO: model spec
_getHint: function(score, evt) {
if (score !== 0 && !score) {
return this.opt.noRatedMsg;
}
var decimal = this._getDecimal(score, 1);
var integer = Math.ceil(score);
var group = this.opt.hints[(integer || 1) - 1];
var hint = group;
var set = !evt || this.isMove;
if (this.opt.precision) {
if (set) {
decimal = decimal === 0 ? 9 : decimal - 1;
}
hint = group[decimal];
} else if (this.opt.halfShow || this.opt.half) {
decimal = set && decimal === 0 ? 1 : decimal > 5 ? 1 : 0;
hint = group[decimal];
}
return hint === '' ? '' : hint || score;
},
// TODO: model spec
_getWidth: function() {
var width = this.stars[0].width || parseFloat(this.stars.eq(0).css('font-size'));
if (!width) {
this._error('Could not get the icon width!');
}
return width;
},
// TODO: model spec
_lock: function() {
var hint = this._getHint(this.scoreField.val());
this.element.style.cursor = '';
this.element.title = hint;
this.scoreField.prop('readonly', true);
this.stars.prop('title', hint);
if (this.cancelButton) {
this.cancelButton.hide();
}
this.self.data('readonly', true);
},
_nameForIndex: function(i) {
return this.opt.score && this.opt.score >= i ? 'starOn' : 'starOff';
},
// TODO: model spec
_resetTitle: function() {
for (var i = 0; i < this.opt.number; i++) {
this.stars[i].title = this._getHint(i + 1);
}
},
// TODO: model spec
_roundHalfScore: function(score) {
var integer = parseInt(score, 10);
var decimal = this._getDecimal(score, 1);
if (decimal !== 0) {
decimal = decimal > 5 ? 1 : 0.5;
}
return integer + decimal;
},
// TODO: model spec
_roundStars: function(score, evt) {
var name = this._starName(score, evt);
if (name) {
var icon = this.opt[name];
var star = this.stars[Math.ceil(score) - 1];
this._setIcon(star, icon);
} // Full down: [x.00 .. x.25]
},
// TODO: model spec
_setIcon: function(star, icon) {
star[this.opt.starType === 'img' ? 'src' : 'className'] = this.opt.path + icon;
},
_setPath: function() {
this.opt.path = this.opt.path || '';
if (this.opt.path && this.opt.path.slice(-1)[0] !== '/') {
this.opt.path += '/';
}
},
// TODO: model spec
_setTarget: function(target, score) {
if (score) {
score = this.opt.targetFormat.toString().replace('{score}', score);
}
if (target.is(':input')) {
target.val(score);
} else {
target.html(score);
}
},
// TODO: model spec
_setTitle: function(score, evt) {
if (score) {
var integer = parseInt(Math.ceil(score), 10);
var star = this.stars[integer - 1];
star.title = this._getHint(score, evt);
}
},
_starName: function(score, evt) {
var decimal = +(score % 1).toFixed(2);
if (evt || this.isMove) {
return decimal > 0.5 ? 'starOn' : 'starHalf';
}
if (decimal <= this.opt.round.down) { // Down: [x.00 ... x.25]
return;
}
if (this.opt.halfShow && decimal < this.opt.round.up) { // Half: [x.26 ... x.75]
return 'starHalf';
}
if (decimal < this.opt.round.full) { // Off: [x.26 .. x.6]
return 'starOff';
}
return 'starOn'; // Up: [x.26 ...] || [x.6 ...]
},
// TODO: model spec
_target: function(score, evt) {
if (this.opt.target) {
var target = $(this.opt.target);
if (!target.length) {
this._error('Target selector invalid or missing!');
}
var mouseover = evt && evt.type === 'mouseover';
if (score === undefined) {
score = this.opt.targetText;
} else if (score === null) {
score = mouseover ? this.opt.cancelHint : this.opt.targetText;
} else {
if (this.opt.targetType === 'hint') {
score = this._getHint(score, evt);
} else if (this.opt.precision) {
score = parseFloat(score).toFixed(1);
}
var mousemove = evt && evt.type === 'mousemove';
if (!mouseover && !mousemove && !this.opt.targetKeep) {
score = this.opt.targetText;
}
}
this._setTarget(target, score);
}
},
// TODO: model spec
_turnOn: function(i, score) {
return this.opt.single ? (i === score) : (i <= score);
},
// TODO: model spec
_unlock: function() {
this.element.style.cursor = 'pointer';
this.element.removeAttribute('title');
this.scoreField.removeAttr('readonly');
this.self.data('readonly', false);
this._resetTitle();
if (this.cancelButton) {
this.cancelButton.css('display', '');
}
},
// TODO: model spec
cancel: function(click) {
if (this.self.data('readonly') !== true) {
this[click ? 'click' : 'score'].call(this, null);
this.scoreField.removeAttr('value');
}
},
// TODO: model spec
click: function(score) {
if (this.self.data('readonly') !== true) {
score = this._adjustedScore(score);
this._apply(score);
if (this.opt.click) {
this.opt.click.call(this.element, score, $.Event('click'));
}
this._target(score);
}
},
// TODO: model spec
getScore: function() {
var score = [];
var value ;
value = this.scoreField.val();
score.push(value ? +value : undefined);
return (score.length > 1) ? score : score[0];
},
// TODO: model spec
move: function(score) {
var integer = parseInt(score, 10);
var decimal = this._getDecimal(score, 1);
if (integer >= this.opt.number) {
integer = this.opt.number - 1;
decimal = 10;
}
var width = this._getWidth();
var steps = width / 10;
var star = $(this.stars[integer]);
var percent = star.offset().left + steps * decimal;
var evt = $.Event('mousemove', { pageX: percent });
this.isMove = true;
star.trigger(evt);
this.isMove = false;
},
// TODO: model spec
readOnly: function(readonly) {
if (this.self.data('readonly') !== readonly) {
if (readonly) {
this.self.off('.raty').children(this.opt.starType).off('.raty');
this._lock();
} else {
this._binds();
this._unlock();
}
this.self.data('readonly', readonly);
}
},
// TODO: model spec
score: function() {
return arguments.length ? this.setScore.apply(this, arguments) : this.getScore();
},
setScore: function(score) {
if (this.self.data('readonly') !== true) {
score = this._adjustedScore(score);
this._apply(score);
this._target(score);
}
}
};
return Raty;
})();
}));

View File

@@ -0,0 +1,6 @@
/**
* Owl Carousel v2.3.4
* Copyright 2013-2018 David Deutsch
* Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
*/
.owl-carousel,.owl-carousel .owl-item{-webkit-tap-highlight-color:transparent;position:relative}.owl-carousel{display:none;width:100%;z-index:1}.owl-carousel .owl-stage{position:relative;-ms-touch-action:pan-Y;touch-action:manipulation;-moz-backface-visibility:hidden}.owl-carousel .owl-stage:after{content:".";display:block;clear:both;visibility:hidden;line-height:0;height:0}.owl-carousel .owl-stage-outer{position:relative;overflow:hidden;-webkit-transform:translate3d(0,0,0)}.owl-carousel .owl-item,.owl-carousel .owl-wrapper{-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0)}.owl-carousel .owl-item{min-height:1px;float:left;-webkit-backface-visibility:hidden;-webkit-touch-callout:none}.owl-carousel .owl-item img{display:block;width:100%}.owl-carousel .owl-dots.disabled,.owl-carousel .owl-nav.disabled{display:none}.no-js .owl-carousel,.owl-carousel.owl-loaded{display:block}.owl-carousel .owl-dot,.owl-carousel .owl-nav .owl-next,.owl-carousel .owl-nav .owl-prev{cursor:pointer;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel .owl-nav button.owl-next,.owl-carousel .owl-nav button.owl-prev,.owl-carousel button.owl-dot{background:0 0;color:inherit;border:none;padding:0!important;font:inherit}.owl-carousel.owl-loading{opacity:0;display:block}.owl-carousel.owl-hidden{opacity:0}.owl-carousel.owl-refresh .owl-item{visibility:hidden}.owl-carousel.owl-drag .owl-item{-ms-touch-action:pan-y;touch-action:pan-y;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.owl-carousel.owl-grab{cursor:move;cursor:grab}.owl-carousel.owl-rtl{direction:rtl}.owl-carousel.owl-rtl .owl-item{float:right}.owl-carousel .animated{animation-duration:1s;animation-fill-mode:both}.owl-carousel .owl-animated-in{z-index:0}.owl-carousel .owl-animated-out{z-index:1}.owl-carousel .fadeOut{animation-name:fadeOut}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}.owl-height{transition:height .5s ease-in-out}.owl-carousel .owl-item .owl-lazy{opacity:0;transition:opacity .4s ease}.owl-carousel .owl-item .owl-lazy:not([src]),.owl-carousel .owl-item .owl-lazy[src^=""]{max-height:0}.owl-carousel .owl-item img.owl-lazy{transform-style:preserve-3d}.owl-carousel .owl-video-wrapper{position:relative;height:100%;background:#000}.owl-carousel .owl-video-play-icon{position:absolute;height:80px;width:80px;left:50%;top:50%;margin-left:-40px;margin-top:-40px;background:url(owl.video.play.png) no-repeat;cursor:pointer;z-index:1;-webkit-backface-visibility:hidden;transition:transform .1s ease}.owl-carousel .owl-video-play-icon:hover{-ms-transform:scale(1.3,1.3);transform:scale(1.3,1.3)}.owl-carousel .owl-video-playing .owl-video-play-icon,.owl-carousel .owl-video-playing .owl-video-tn{display:none}.owl-carousel .owl-video-tn{opacity:0;height:100%;background-position:center center;background-repeat:no-repeat;background-size:contain;transition:opacity .4s ease}.owl-carousel .owl-video-frame{position:relative;z-index:1;height:100%;width:100%}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,6 @@
/**
* Owl Carousel v2.3.4
* Copyright 2013-2018 David Deutsch
* Licensed under: SEE LICENSE IN https://github.com/OwlCarousel2/OwlCarousel2/blob/master/LICENSE
*/
.owl-theme .owl-dots,.owl-theme .owl-nav{text-align:center;-webkit-tap-highlight-color:transparent}.owl-theme .owl-nav{margin-top:10px}.owl-theme .owl-nav [class*=owl-]{color:#FFF;font-size:14px;margin:5px;padding:4px 7px;background:#D6D6D6;display:inline-block;cursor:pointer;border-radius:3px}.owl-theme .owl-nav [class*=owl-]:hover{background:#869791;color:#FFF;text-decoration:none}.owl-theme .owl-nav .disabled{opacity:.5;cursor:default}.owl-theme .owl-nav.disabled+.owl-dots{margin-top:10px}.owl-theme .owl-dots .owl-dot{display:inline-block;zoom:1}.owl-theme .owl-dots .owl-dot span{width:10px;height:10px;margin:5px 7px;background:#D6D6D6;display:block;-webkit-backface-visibility:visible;transition:opacity .2s ease;border-radius:30px}.owl-theme .owl-dots .owl-dot.active span,.owl-theme .owl-dots .owl-dot:hover span{background:#869791}

View File

@@ -0,0 +1,97 @@
@extends('themes::september.layout')
@php
$years = Cache::remember('all_years', \Backpack\Settings\app\Models\Setting::get('site_cache_ttl', 5 * 60), function () {
return \VsMov\Core\Models\Movie::select('publish_year')
->distinct()
->pluck('publish_year')
->sortDesc();
});
@endphp
@section('breadcrumb')
<div class="breadcrumb block md:flex items-center container mx-auto mt-2 w-full line-clamp-1">
<div class="ml-2 md:ml-0">
<ol class="flex flex-wrap items-center gap-1" itemScope itemType="https://schema.org/BreadcrumbList">
<li itemProp="itemListElement" itemScope itemType="http://schema.org/ListItem">
<a class="flex items-center gap-x-1" itemProp="item" title="Xem phim" href="/">
<span class="flex items-center gap-x-1 text-main-primary hover:text-main-warning" itemProp="name">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 21v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21m0 0h4.5V3.545M12.75 21h7.5V10.75M2.25 21h1.5m18 0h-18M2.25 9l4.5-1.636M18.75 3l-1.5.545m0 6.205l3 1m1.5.5l-1.5-.5M6.75 7.364V3h-3v18m3-13.636l10.5-3.819" />
</svg>
Xem phim
</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemProp="position" content="1" />
</a>
</li>
<li class="inline text-gray-400" itemprop="itemListElement" itemscope=""
itemtype="http://schema.org/ListItem">
<a itemprop="item" href="{{ url()->current() }}" title="{{ $section_name ?? 'Danh Sách Phim' }}">
<span itemprop="name">
{{ $section_name ?? 'Danh Sách Phim' }}
</span>
</a>
<meta itemprop="position" content="2">
</li>
</ol>
</div>
</div>
@endsection
@section('content')
@include('themes::september.inc.catalog_filter')
<section class="w-full mt-2">
<div class="flex justify-between">
<div class="py-2 w-max">
<h3
class="text-base md:text-2xl uppercase font-semibold text-transparent bg-clip-text bg-gradient-to-r from-[#7367F0] to-[#8e84fc]">
{{ $section_name }}</h3>
<div class="w-full h-0.5 bg-main-indigo bg-gradient-to-r from-[#7367F0] to-[#8e84fc]"></div>
</div>
</div>
@if (count($data))
<div
class="grid grid-flow-row grid-cols-2 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-6 gap-2">
@foreach ($data ?? [] as $movie)
<a href="{{ $movie->getUrl() }}" class="overflow-hidden relative group"
title="{{ $movie->name ?? '' }}">
<img style="aspect-ratio: 256/340" data-src="{{ $movie->getThumbUrl() }}"
alt="{{ $movie->name ?? '' }}"
class="group-hover:opacity-60 transition-all duration-500 transform group-hover:scale-110 lazyload" />
<span
class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">{{ $movie->name }}
</div>
<div class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<span class="absolute top-0 left-0 p-0.5 bg-main-primary text-sm">
{{ $movie->episode_current }}
</span>
<div class="absolute hidden top-1/3 left-1/3 animate-pulse group-hover:block">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-16 h-16 text-main-warning">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.91 11.672a.375.375 0 010 .656l-5.603 3.113a.375.375 0 01-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112z" />
</svg>
</div>
</a>
@endforeach
</div>
@else
<div class="flex flex-row flex-wrap flex-grow h-50 mt-10">
<p class="w-full text-center text-white">Rất tiếc, không có nội dung nào trùng khớp yêu cầu.</p>
</div>
@endif
{{ $data->appends(request()->all())->links('themes::september.inc.pagination') }}
</section>
@endsection

View File

@@ -0,0 +1,608 @@
@extends('themes::september.layout')
@section('breadcrumb')
<div class="breadcrumb block md:flex items-center container mx-auto mt-2 w-full line-clamp-1">
<div class="ml-2 md:ml-0">
<ol class="flex flex-wrap items-center gap-1" itemScope itemType="https://schema.org/BreadcrumbList">
<li itemProp="itemListElement" itemScope itemType="http://schema.org/ListItem">
<a class="flex items-center gap-x-1" itemProp="item" title="Xem phim" href="/">
<span class="flex items-center gap-x-1 text-main-primary hover:text-main-warning" itemProp="name">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 21v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21m0 0h4.5V3.545M12.75 21h7.5V10.75M2.25 21h1.5m18 0h-18M2.25 9l4.5-1.636M18.75 3l-1.5.545m0 6.205l3 1m1.5.5l-1.5-.5M6.75 7.364V3h-3v18m3-13.636l10.5-3.819" />
</svg>
Xem phim
</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemProp="position" content="1" />
</a>
</li>
@foreach ($currentMovie->regions as $region)
<li itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a class="text-main-primary hover:text-main-warning" itemprop="item" href="{{ $region->getUrl() }}"
title="{{ $region->name }}">
<span itemprop="name">
{{ $region->name }}
</span>
</a>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemprop="position" content="3">
</li>
@endforeach
@foreach ($currentMovie->categories as $category)
<li itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a class="text-main-primary hover:text-main-warning" itemprop="item"
href="{{ $category->getUrl() }}" title="{{ $category->name }}">
<span itemprop="name">
{{ $category->name }}
</span>
</a>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemprop="position" content="3">
</li>
@endforeach
<li itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a class="text-main-primary hover:text-main-warning" itemprop="item"
href="{{ $currentMovie->getUrl() }}" title="{{ $currentMovie->name }}">
<span itemprop="name">
{{ $currentMovie->name }}
</span>
</a>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemprop="position" content="4">
</li>
<li class="inline text-gray-400" itemprop="itemListElement" itemscope=""
itemtype="http://schema.org/ListItem">
<a itemprop="item" href="{{ url()->current() }}" title="Tập {{ $episode->name }}">
<span itemprop="name">
Tập {{ $episode->name }}
</span>
</a>
<meta itemprop="position" content="5">
</li>
</ol>
</div>
</div>
@endsection
@section('player_wraper')
<div class="w-full table relative container mx-auto my-2 px-2 lg:px-0">
<div class="relative table-cell bg-main-800/40">
<div class="relative iframe w-full" style="aspect-ratio: 16 / 9;" id="player-wrapper">
</div>
<div class="flex justify-between justify-items-center items-center w-full h-[44px] p-1">
<div class="flex items-center">
<div
class="text-sm md:text-base py-2.5 flex flex-wrap md:grid grid-flow-row grid-cols-5 gap-1 md:gap-2">
@foreach ($currentMovie->episodes->where('slug', $episode->slug)->where('server', $episode->server) as $server)
<a class="streaming-server relative w-content text-center bg-main-labelbgSecondary text-white hover:shadow-menu hover:bg-main-primary duration-150 px-2 md:px-3 py-1 cursor-pointer"
onclick="chooseStreamingServer(this)" data-type="{{ $server->type }}"
data-id="{{ $server->id }}" data-link="{{ $server->link }}">
<span>VIP #{{ $loop->index + 1 }}</span>
</a>
@endforeach
</div>
</div>
<div class="block lg:flex items-center">
<span id="toggleModal-report"
class="w-content text-center bg-main-danger text-white hover:shadow-menu hover:bg-main-primary duration-150 px-2 md:px-3 py-1 cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 12.75c1.148 0 2.278.08 3.383.237 1.037.146 1.866.966 1.866 2.013 0 3.728-2.35 6.75-5.25 6.75S6.75 18.728 6.75 15c0-1.046.83-1.867 1.866-2.013A24.204 24.204 0 0112 12.75zm0 0c2.883 0 5.647.508 8.207 1.44a23.91 23.91 0 01-1.152 6.06M12 12.75c-2.883 0-5.647.508-8.208 1.44.125 2.104.52 4.136 1.153 6.06M12 12.75a2.25 2.25 0 002.248-2.354M12 12.75a2.25 2.25 0 01-2.248-2.354M12 8.25c.995 0 1.971-.08 2.922-.236.403-.066.74-.358.795-.762a3.778 3.778 0 00-.399-2.25M12 8.25c-.995 0-1.97-.08-2.922-.236-.402-.066-.74-.358-.795-.762a3.734 3.734 0 01.4-2.253M12 8.25a2.25 2.25 0 00-2.248 2.146M12 8.25a2.25 2.25 0 012.248 2.146M8.683 5a6.032 6.032 0 01-1.155-1.002c.07-.63.27-1.222.574-1.747m.581 2.749A3.75 3.75 0 0115.318 5m0 0c.427-.283.815-.62 1.155-.999a4.471 4.471 0 00-.575-1.752M4.921 6a24.048 24.048 0 00-.392 3.314c1.668.546 3.416.914 5.223 1.082M19.08 6c.205 1.08.337 2.187.392 3.314a23.882 23.882 0 01-5.223 1.082" />
</svg>
Báo lỗi
</span>
</div>
</div>
</div>
<div class="episode relative table-row lg:table-cell overflow-hidden lg:w-1/4 bg-main-800/40">
<div class="lg:absolute inset-0">
<div class="hidden lg:block text-xl font-bold m-2">{{ $currentMovie->name }}</div>
<div class="flex flex-wrap" id="tabs-id">
<div class="w-full">
<ul class="flex mb-0 list-none flex-wrap pt-3 flex-row p-1 lg:p-0">
@foreach ($currentMovie->episodes->sortBy([['server', 'asc']])->groupBy('server') as $server => $data)
<li class="-mb-px mr-2 last:mr-0 flex-auto text-center cursor-pointer">
<a class="text-xs font-bold uppercase px-1 py-2 shadow-lg block leading-normal text-white bg-main-labelbgSecondary @if ($episode->server == $server) bg-main-primary @endif hover:shadow-menu hover:bg-main-primary"
onclick="changeAtiveTab(event,'tab-server-{{ $loop->index }}')">
@if ($episode->server == $server)
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 01-3-3m3 3a3 3 0 100 6h13.5a3 3 0 100-6m-16.5-3a3 3 0 013-3h13.5a3 3 0 013 3m-19.5 0a4.5 4.5 0 01.9-2.7L5.737 5.1a3.375 3.375 0 012.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 01.9 2.7m0 0a3 3 0 01-3 3m0 3h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008zm-3 6h.008v.008h-.008v-.008zm0-6h.008v.008h-.008v-.008z" />
</svg>
@endif {{ $server }}
</a>
</li>
@endforeach
</ul>
<div class="relative tab-content tab-space mt-1 p-2 scroll overflow-auto max-h-32 md:max-h-[345px] lg:max-h-[370px] xl:max-h-[475px] 2xl:max-h-[602px]">
@foreach ($currentMovie->episodes->sortBy([['server', 'asc']])->groupBy('server') as $server => $data)
<div class="@if ($episode->server != $server) hidden @endif grid grid-flow-row grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 gap-2"
id="tab-server-{{ $loop->index }}">
@foreach ($data->sortByDesc('name', SORT_NATURAL)->groupBy('name') as $name => $item)
<a class="relative w-content text-center bg-main-labelbgSecondary @if ($item->contains($episode)) bg-main-primary @endif text-white hover:shadow-menu hover:bg-main-primary duration-150 py-1 overflow-hidden overflow-ellipsis whitespace-nowrap"
title="{{ $name }}" href="{{ $item->sortByDesc('type')->first()->getUrl() }}">
{{ $name }}
@if ($item->contains($episode))
<span class="flex absolute h-3 w-3 top-0 right-0 -mt-1 -mr-1">
<span
class="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75"></span>
<span
class="relative inline-flex rounded-full h-3 w-3 bg-orange-500"></span>
</span>
@endif
</a>
@endforeach
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('content')
<div class="bg-main-800/40 p-2 mt-2 md:flex flex-wrap">
<div class="relative w-full h-full pt-0 md:w-1/3 lg:w-1/3 xl:w-1/6">
<img style="aspect-ratio: 266/400" src="{{ $currentMovie->getThumbUrl() }}" alt="{{ $currentMovie->name }}"
class="w-full h-auto max-h-96" />
</div>
<div class="w-full md:w-2/3 lg:w-2/3 xl:w-5/6 md:pl-2 pt-0 md:mt-0 text-sm">
<div class="bg-main-800/40 text-center py-1">
<h1 class="uppercase text-xl font-extrabold text-main-primary">Xem phim {{ $currentMovie->name }} Tập
{{ $episode->name }}</h1>
<h2 class="italic text-main-orange">{{ $currentMovie->origin_name }}</h2>
<div class="items-center text-center gap-x-2">
<div id="movies-rating-star" class="flex justify-center" style="height: 18px;"></div>
<div class="text-white align-middle">
({{$currentMovie->getRatingStar()}}
sao
/
{{$currentMovie->getRatingCount()}} đánh giá)
</div>
<div id="movies-rating-msg" class="text-[#FDB813] mb-2 font-bold text-sm mt-2"></div>
</div>
</div>
<article class="block bg-main-800/40 p-2 mt-2">
<div class="border-b-[1px] border-main-secondary border-opacity-25 py-1 scroll overflow-auto max-h-32">
@if ($currentMovie->content)
{!! $currentMovie->content !!}
@else
<p>Đang cập nhật ...</p>
@endif
</div>
<div>
<ul class="flex flex-wrap gap-1 py-1">
@foreach ($currentMovie->tags as $tag)
<li class="bg-main-labelbgPrimary text-white hover:text-main-warning px-2">
<a href="{{ $tag->getUrl() }}" title="{{ $tag->name }}">{{ $tag->name }}</a>
</li>
@endforeach
</ul>
</div>
</article>
</div>
</div>
<div class="bg-white mt-2 w-full" style="background-color: #ffffff">
<div class="fb-comments w-full" data-href="{{ $currentMovie->getUrl() }}" data-width="100%"
data-numposts="5" data-colorscheme="light" data-lazy="true">
</div>
</div>
<section class="w-full mt-2">
<div class="flex justify-between">
<div class="py-2 w-max">
<h3
class="text-base md:text-2xl uppercase font-semibold text-transparent bg-clip-text bg-gradient-to-r from-[#7367F0] to-[#8e84fc]">
Có thể bạn muốn xem</h3>
</div>
</div>
@php
$item['data'] = $movie_related;
@endphp
@include('themes::september.inc.section.section_thumb')
</section>
<div id="modal-report" class="relative z-10 hidden" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="fixed inset-0 bg-black bg-opacity-75 transition-opacity"></div>
<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div id="modal-report-form"
class="relative transform overflow-hidden text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div class=" bg-main-800/60 p-2">
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-red-600">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="w-full mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg font-medium leading-6 text-gray-100" id="modal-title">Báo lỗi phim</h3>
<div class="mt-2">
<textarea id="report_message" class="w-full p-3 bg-main-900 text-white focus:outline-none" rows="5"
placeholder="Hãy nhập nội dung lỗi để chúng mình sửa nhanh hơn...">Không tải được tập phim</textarea>
</div>
</div>
</div>
</div>
<div class="bg-main-900/60 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" id="report_episode_btn"
class="inline-flex w-full justify-center border border-transparent bg-main-primary px-2 py-1 text-base font-medium text-white shadow-sm hover:bg-main-primary/80 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:ml-3 sm:w-auto sm:text-sm">Gửi</button>
<button type="button" id="close-report-modal"
class="mt-3 inline-flex w-full justify-center border border-gray-300 bg-white px-2 py-1 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">Đóng</button>
</div>
</div>
</div>
</div>
</div>
@endsection
@push('scripts')
<script src="/themes/september/player/js/p2p-media-loader-core.min.js"></script>
<script src="/themes/september/player/js/p2p-media-loader-hlsjs.min.js"></script>
<script src="/js/jwplayer-8.9.3.js"></script>
<script src="/js/hls.min.js"></script>
<script src="/js/jwplayer.hlsjs.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$("#toggleModal-report").click(() => {
$("#modal-report").toggle("hidden");
$("#modal-report-backdrop").toggle("hidden");
})
$('body').click(function(event) {
if (($(event.target).closest("#modal-report").length && !$(event.target).closest(
"#modal-report-form")
.length) || $(event.target).closest("#close-report-modal").length) {
$("#modal-report").toggle("hidden");
$("#modal-report-backdrop").toggle("hidden");
}
});
});
</script>
<script>
$(document).ready(function() {
$('html, body').animate({
scrollTop: $('#player-wrapper').offset().top - 40
}, 'slow');
});
</script>
<script type="text/javascript">
function changeAtiveTab(event, tabID) {
let element = event.target;
while (element.nodeName !== "A") {
element = element.parentNode;
}
ulElement = element.parentNode.parentNode;
aElements = ulElement.querySelectorAll("li > a");
tabContents = document.getElementById("tabs-id").querySelectorAll(".tab-content > div");
for (let i = 0; i < aElements.length; i++) {
aElements[i].classList.remove("text-white");
aElements[i].classList.remove("bg-main-primary");
aElements[i].classList.add("text-white");
aElements[i].classList.add("bg-main-labelbgSecondary");
tabContents[i].classList.add("hidden");
tabContents[i].classList.remove("block");
}
element.classList.remove("text-white");
element.classList.remove("bg-main-labelbgSecondary");
element.classList.add("text-white");
element.classList.add("bg-main-primary");
document.getElementById(tabID).classList.remove("hidden");
document.getElementById(tabID).classList.add("block");
}
</script>
<script>
var episode_id = {{$episode->id}};
const wrapper = document.getElementById('player-wrapper');
const vastAds = "{{ Setting::get('jwplayer_advertising_file') }}";
function chooseStreamingServer(el) {
const type = el.dataset.type;
const link = el.dataset.link.replace(/^http:\/\//i, 'https://');
const id = el.dataset.id;
const newUrl =
location.protocol +
"//" +
location.host +
location.pathname.replace(`-${episode_id}`, `-${id}`);
history.pushState({
path: newUrl
}, "", newUrl);
episode_id = id;
Array.from(document.getElementsByClassName('streaming-server')).forEach(server => {
server.classList.remove('bg-main-primary');
})
el.classList.add('bg-main-primary');
document.getElementById('stream-server-active')?.remove();
el.innerHTML +=
`<span id="stream-server-active" class="flex absolute h-3 w-3 top-0 right-0 -mt-1 -mr-1"><span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-purple-400 opacity-75"></span><span class="relative inline-flex rounded-full h-3 w-3 bg-purple-500"></span></span>`;
renderPlayer(type, link, id);
}
function renderPlayer(type, link, id) {
if (type == 'embed') {
if (vastAds) {
wrapper.innerHTML = `<div id="fake_jwplayer"></div>`;
const fake_player = jwplayer("fake_jwplayer");
const objSetupFake = {
key: "{{ Setting::get('jwplayer_license') }}",
aspectratio: "16:9",
width: "100%",
file: "/themes/september/player/1s_blank.mp4",
volume: 100,
mute: false,
autostart: true,
advertising: {
tag: "{{ Setting::get('jwplayer_advertising_file') }}",
client: "vast",
vpaidmode: "insecure",
skipoffset: {{ (int) Setting::get('jwplayer_advertising_skipoffset') ?: 5 }}, // Bỏ qua quảng cáo trong vòng 5 giây
skipmessage: "Bỏ qua sau xx giây",
skiptext: "Bỏ qua"
}
};
fake_player.setup(objSetupFake);
fake_player.on('complete', function(event) {
$("#fake_jwplayer").remove();
wrapper.innerHTML = `<iframe width="100%" height="100%" src="${link}" frameborder="0" scrolling="no"
allowfullscreen="" allow='autoplay'></iframe>`
fake_player.remove();
});
fake_player.on('adSkipped', function(event) {
$("#fake_jwplayer").remove();
wrapper.innerHTML = `<iframe width="100%" height="100%" src="${link}" frameborder="0" scrolling="no"
allowfullscreen="" allow='autoplay'></iframe>`
fake_player.remove();
});
fake_player.on('adComplete', function(event) {
$("#fake_jwplayer").remove();
wrapper.innerHTML = `<iframe width="100%" height="100%" src="${link}" frameborder="0" scrolling="no"
allowfullscreen="" allow='autoplay'></iframe>`
fake_player.remove();
});
} else {
if (wrapper) {
wrapper.innerHTML = `<iframe width="100%" height="100%" src="${link}" frameborder="0" scrolling="no"
allowfullscreen="" allow='autoplay'></iframe>`
}
}
return;
}
if (type == 'm3u8' || type == 'mp4') {
wrapper.innerHTML = `<div id="jwplayer"></div>`;
const player = jwplayer("jwplayer");
const objSetup = {
key: "{{ Setting::get('jwplayer_license') }}",
aspectratio: "16:9",
width: "100%",
image: "{{ $currentMovie->getPosterUrl() }}",
file: link,
playbackRateControls: true,
playbackRates: [0.25, 0.75, 1, 1.25],
sharing: {
sites: [
"reddit",
"facebook",
"twitter",
"googleplus",
"email",
"linkedin",
],
},
volume: 100,
mute: false,
autostart: true,
logo: {
file: "{{ Setting::get('jwplayer_logo_file') }}",
link: "{{ Setting::get('jwplayer_logo_link') }}",
position: "{{ Setting::get('jwplayer_logo_position') }}",
},
advertising: {
tag: "{{ Setting::get('jwplayer_advertising_file') }}",
client: "vast",
vpaidmode: "insecure",
skipoffset: {{ (int) Setting::get('jwplayer_advertising_skipoffset') ?: 5 }}, // Bỏ qua quảng cáo trong vòng 5 giây
skipmessage: "Bỏ qua sau xx giây",
skiptext: "Bỏ qua"
}
};
if (type == 'm3u8') {
const segments_in_queue = 50;
var engine_config = {
debug: !1,
segments: {
forwardSegmentCount: 50,
},
loader: {
cachedSegmentExpiration: 864e5,
cachedSegmentsCount: 1e3,
requiredSegmentsPriority: segments_in_queue,
httpDownloadMaxPriority: 9,
httpDownloadProbability: 0.06,
httpDownloadProbabilityInterval: 1e3,
httpDownloadProbabilitySkipIfNoPeers: !0,
p2pDownloadMaxPriority: 50,
httpFailedSegmentTimeout: 500,
simultaneousP2PDownloads: 20,
simultaneousHttpDownloads: 2,
// httpDownloadInitialTimeout: 12e4,
// httpDownloadInitialTimeoutPerSegment: 17e3,
httpDownloadInitialTimeout: 0,
httpDownloadInitialTimeoutPerSegment: 17e3,
httpUseRanges: !0,
maxBufferLength: 300,
// useP2P: false,
},
};
if (Hls.isSupported() && p2pml.hlsjs.Engine.isSupported()) {
var engine = new p2pml.hlsjs.Engine(engine_config);
player.setup(objSetup);
jwplayer_hls_provider.attach();
p2pml.hlsjs.initJwPlayer(player, {
liveSyncDurationCount: segments_in_queue, // To have at least 7 segments in queue
maxBufferLength: 300,
loader: engine.createLoaderClass(),
});
} else {
player.setup(objSetup);
}
} else {
player.setup(objSetup);
}
const resumeData = 'OPCMS-PlayerPosition-' + id;
player.on('ready', function() {
if (typeof(Storage) !== 'undefined') {
if (localStorage[resumeData] == '' || localStorage[resumeData] == 'undefined') {
console.log("No cookie for position found");
var currentPosition = 0;
} else {
if (localStorage[resumeData] == "null") {
localStorage[resumeData] = 0;
} else {
var currentPosition = localStorage[resumeData];
}
console.log("Position cookie found: " + localStorage[resumeData]);
}
player.once('play', function() {
console.log('Checking position cookie!');
console.log(Math.abs(player.getDuration() - currentPosition));
if (currentPosition > 180 && Math.abs(player.getDuration() - currentPosition) >
5) {
player.seek(currentPosition);
}
});
window.onunload = function() {
localStorage[resumeData] = player.getPosition();
}
} else {
console.log('Your browser is too old!');
}
});
player.on('complete', function() {
if (typeof(Storage) !== 'undefined') {
localStorage.removeItem(resumeData);
} else {
console.log('Your browser is too old!');
}
})
function formatSeconds(seconds) {
var date = new Date(1970, 0, 1);
date.setSeconds(seconds);
return date.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, "$1");
}
}
}
</script>
<script>
document.addEventListener("DOMContentLoaded", function() {
const episode = '{{$episode->id}}';
let playing = document.querySelector(`[data-id="${episode}"]`);
if (playing) {
playing.click();
return;
}
const servers = document.getElementsByClassName('streaming-server');
if (servers[0]) {
servers[0].click();
}
});
</script>
<script>
$("#report_episode_btn").click(() => {
fetch("{{ route('episodes.report', ['movie' => $currentMovie->slug, 'episode' => $episode->slug, 'id' => $episode->id]) }}", {
method: 'POST',
headers: {
"Content-Type": "application/json",
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
'content')
},
body: JSON.stringify({
message: document.getElementById('report_message')
.innerHTML ??
''
})
});
$("#modal-report").toggle("hidden");
$("#modal-report-backdrop").toggle("hidden");
$("#toggleModal-report").remove();
})
</script>
<script src="/themes/september/plugins/jquery-raty/jquery.raty.js"></script>
<link href="/themes/september/plugins/jquery-raty/jquery.raty.css" rel="stylesheet" type="text/css" />
<script>
var rated = false;
$('#movies-rating-star').raty({
score: {{$currentMovie->getRatingStar()}},
number: 10,
numberMax: 10,
hints: ['quá tệ', 'tệ', 'không hay', 'không hay lắm', 'bình thường', 'xem được', 'có vẻ hay', 'hay',
'rất hay', 'siêu phẩm'
],
starOff: '/themes/september/plugins/jquery-raty/images/star-off.png',
starOn: '/themes/september/plugins/jquery-raty/images/star-on.png',
starHalf: '/themes/september/plugins/jquery-raty/images/star-half.png',
click: function(score, evt) {
if (rated) return
fetch("{{ route('movie.rating', ['movie' => $currentMovie->slug]) }}", {
method: 'POST',
headers: {
"Content-Type": "application/json",
'X-CSRF-TOKEN': document.querySelector(
'meta[name="csrf-token"]')
.getAttribute(
'content')
},
body: JSON.stringify({
rating: score
})
});
rated = true;
$('#movies-rating-star').data('raty').readOnly(true);
$('#movies-rating-msg').html(`Bạn đã đánh giá ${score} sao cho phim này!`);
}
});
</script>
{!! setting('site_scripts_facebook_sdk') !!}
@endpush

View File

@@ -0,0 +1,10 @@
@foreach ($tops as $top)
<div class="bg-main-800/40 p-2 mt-2">
<div class="text-center">
<h3 class="font-semibold uppercase">{{ $top['label'] }}</h3>
</div>
<div class="space-y-1 block">
@include('themes::september.inc.aside.' . $top['template'])
</div>
</div>
@endforeach

View File

@@ -0,0 +1,54 @@
@foreach ($top['data'] ?? [] as $movie)
@if ($loop->first)
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name }}" class="block overflow-hidden relative group">
<img src="{{ $movie->getPosterUrl() }}"
class="group-hover:opacity-60 transition-all duration-500 w-full h-[200px]" alt="{{ $movie->name }}" />
<span class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">{{ $movie->name }}</div>
<div class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<div class="absolute top-0 right-0 bg-main-800/40 p-2 flex gap-x-1 items-center text-main-success">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span class="text-xs">
{{ $movie->view_week ?? 0 }}
</span>
</div>
</a>
@else
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name }}" class="group">
<div
class="flex justify-start items-center space-x-1 py-1 border-b-[1px] border-main-secondary border-dashed border-opacity-25">
<div
class="w-6 text-center bg-main-primary group-hover:text-main-warning bg-opacity-10 text-main-primary font-bold">
{{ $loop->index + 1 }}</div>
<div class="basis-4/6 flex-grow px-2 space-y-2 overflow-hidden">
<div
class="font-bold text-main-primary text-sm group-hover:text-main-warning transition duration-300 ease-in-out overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->name }}</div>
</div>
<div class="flex gap-x-0.5 items-center text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span class="text-xs">
{{ $movie->view_week ?? 0 }}
</span>
</div>
</div>
</a>
@endif
@endforeach

View File

@@ -0,0 +1,32 @@
@foreach ($top['data'] ?? [] as $movie)
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name }}" class="group">
<div
class="flex justify-start items-center space-x-1 py-1 border-b-[1px] border-main-secondary border-dashed border-opacity-25">
<div
class="w-6 text-center bg-main-primary group-hover:text-main-warning bg-opacity-10 text-main-primary font-bold">
{{ $loop->index + 1 }}</div>
<div class="w-[45px] h-[57px]">
<img data-src="{{ $movie->getThumbUrl() }}" alt="{{ $movie->origin_name }} ({{ $movie->publish_year }})"
class="group-hover:opacity-60 w-full h-full transition duration-300 ease-in-out lazyload" />
</div>
<div class="basis-4/6 flex-grow px-2 space-y-2 overflow-hidden">
<div
class="font-bold text-main-primary text-sm group-hover:text-main-warning transition duration-300 ease-in-out overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->name }}</div>
<div class="italic text-xs text-gray-400 overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})</div>
</div>
<div class="flex gap-x-0.5 items-center text-gray-400">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span class="text-xs">
{{ $movie->view_week ?? 0 }}
</span>
</div>
</div>
</a>
@endforeach

View File

@@ -0,0 +1,60 @@
<div class="w-full container mx-auto mt-2 bg-main-800/40">
<div class="text-gray-50">
<form id="form-filter" class="lg:flex flex-wrap gap-1 items-center">
<div class="p-2 flex justify-between">
Lọc Phim
</div>
<div class="p-2">
<select name="filter[sort]" form="form-search" class="scroll bg-main-700 p-2 outline-none">
<option value="">Sắp xếp</option>
<option value="update" @if (isset(request('filter')['sort']) && request('filter')['sort'] == 'update') selected @endif>Thời gian cập nhật</option>
<option value="create" @if (isset(request('filter')['sort']) && request('filter')['sort'] == 'create') selected @endif>Thời gian đăng</option>
<option value="year" @if (isset(request('filter')['sort']) && request('filter')['sort'] == 'year') selected @endif>Năm sản xuất</option>
<option value="view" @if (isset(request('filter')['sort']) && request('filter')['sort'] == 'view') selected @endif>Lượt xem</option>
</select>
</div>
<div class="p-2">
<select name="filter[type]" form="form-search" class="scroll bg-main-700 p-2 outline-none">
<option value="">Mọi định dạng</option>
<option value="series" @if (isset(request('filter')['type']) && request('filter')['type'] == 'series') selected @endif>Phim bộ</option>
<option value="single" @if (isset(request('filter')['type']) && request('filter')['type'] == 'single') selected @endif>Phim lẻ</option>
</select>
</div>
<div class="p-2">
<select name="filter[category]" form="form-search" class="scroll bg-main-700 p-2 outline-none">
<option value="">Tất cả thể loại</option>
@foreach (\VsMov\Core\Models\Category::fromCache()->all() as $item)
<option value="{{ $item->id }}" @if ((isset(request('filter')['category']) && request('filter')['category'] == $item->id) ||
(isset($category) && $category->id == $item->id)) selected @endif>
{{ $item->name }}</option>
@endforeach
</select>
</div>
<div class="p-2">
<select name="filter[region]" form="form-search" class="scroll bg-main-700 p-2 outline-none">
<option value="">Tất cả quốc gia</option>
@foreach (\VsMov\Core\Models\Region::fromCache()->all() as $item)
<option value="{{ $item->id }}" @if ((isset(request('filter')['region']) && request('filter')['region'] == $item->id) ||
(isset($region) && $region->id == $item->id)) selected @endif>
{{ $item->name }}</option>
@endforeach
</select>
</div>
<div class="p-2">
<select name="filter[year]" form="form-search" class="scroll bg-main-700 p-2 outline-none">
<option value="">Tất cả năm</option>
@foreach ($years as $year)
<option value="{{ $year }}" @if (isset(request('filter')['year']) && request('filter')['year'] == $year) selected @endif>
{{ $year }}</option>
@endforeach
</select>
</div>
<div class="p-2">
<button type="submit" form="form-search"
class="w-content text-center bg-main-labelbgSecondary text-white hover:shadow-menu hover:bg-main-primary duration-150 p-1.5 overflow-hidden overflow-ellipsis whitespace-nowrap">
<span>Duyệt phim</span>
</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,41 @@
@php
$logo = setting('site_logo', '');
$brand = setting('site_brand', '');
$title = isset($title) ? $title : setting('site_homepage_title', '');
@endphp
<!-- BEGIN HEADER -->
<header class="w-full bg-main-800 px-1 xl:px-7 py-3 shadow-md">
<div class="container mx-auto flex justify-between items-center">
<div id="header-search-form" class="relative w-96 hidden xl:block">
<form id="form-search" method="GET" action="/">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 absolute ml-3 mt-2 text-xl">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 15.75l-2.489-2.489m0 0a3.375 3.375 0 10-4.773-4.773 3.375 3.375 0 004.774 4.774zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<input
class="appearance-none border border-input-border rounded-3xl w-full py-2 px-3 bg-main-700 text-gray-300 leading-tight focus: focus:outline-none focus:shadow-outline focus:border-input-focus pl-10"
type="text" name="search" placeholder="Tìm kiếm phim..." value="{{ request('search') }}" />
</form>
</div>
<div>
<a href="/" class="navbar-brand">
@if ($logo)
{!! $logo !!}
@else
{!! $brand !!}
@endif
</a>
</div>
<div></div>
<div></div>
<div class="flex xl:hidden gap-x-4 px-2">
<svg id="header-search-button" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 block xl:hidden cursor-pointer">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>
<svg id="header-menu-button" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 block xl:hidden cursor-pointer">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 5.25h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5m-16.5 4.5h16.5" />
</svg>
</div>
</div>
</header>
<!-- END HEADER -->

View File

@@ -0,0 +1,37 @@
<!-- BEGIN NAVBAR -->
<nav id="nav-menu"
class="hidden xl:block container mx-auto py-0 px-2 bg-main-700 xl:bg-main-800 xl:mt-5 shadow-navbar rounded-md rounded-t-none xl:rounded-t">
<ul class="w-full xl:flex items-center bg-main-700 xl:bg-main-800 px-1 py-1 xl:space-x-4 text-[#D0D2D6]">
@foreach ($menu as $item)
<li
class="group leading-6 cursor-pointer hover:bg-[#161D31] transition duration-150 ease-in-out py-2 my-1 rounded-md px-4 xl:px-2 tracking-wide relative block xl:flex xl:justify-between items-center space-x-1">
@if (count($item['children']))
<span>{{ $item->name }}</span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline" fill="none" viewBox="0 0 24 24"
stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
<div class="group-hover:block hidden top-full left-0 xl:absolute h-auto pt-4 z-20">
<ul
class="w-full xl:w-max grid grid-flow-rows grid-cols-2 md:grid-cols-4 gap-1 bg-main-700 xl:bg-main-800 rounded-md xl:shadow max-h-48 md:max-h-full overflow-auto">
@foreach ($item['children'] as $children)
<li class="py-1 px-2 xl:py-2 xl:px-4">
<a href="{{ $children['link'] }}"
class="block text-gray-300 text-base cursor-pointer hover:translate-x-1 transition duration-200">
{{ $children['name'] }}
</a>
</li>
@endforeach
</ul>
</div>
@else
<a href="{{ $item['link'] }}"
class="block text-gray-300 text-base cursor-pointer transition duration-200">
{{ $item['name'] }}
</a>
@endif
</li>
@endforeach
</ul>
</nav>
<!-- END NAVBAR -->

View File

@@ -0,0 +1,121 @@
@if ($paginator->hasPages())
<nav role="navigation" aria-label="{{ __('Pagination Navigation') }}" class="flex items-center justify-between my-2">
<div class="flex justify-between flex-1 md:hidden">
@if ($paginator->onFirstPage())
<div></div>
{{-- <span class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-500 bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md cursor-default leading-5 rounded-md">
{!! __('pagination.previous') !!}
</span> --}}
@else
<a href="{{ $paginator->previousPageUrl() }}"
class="relative inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md leading-5 rounded-md">
{!! __('pagination.previous') !!}
</a>
@endif
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}"
class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-white bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md leading-5 rounded-md">
{!! __('pagination.next') !!}
</a>
@else
{{-- <span class="relative inline-flex items-center px-4 py-2 ml-3 text-sm font-medium text-gray-500 bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md cursor-default leading-5 rounded-md">
{!! __('pagination.next') !!}
</span> --}}
@endif
</div>
<div class="hidden md:flex-1 md:flex md:items-center md:justify-between">
<div>
<p class="text-sm text-white leading-5">
{!! __('Showing') !!}
@if ($paginator->firstItem())
<span class="font-medium">{{ $paginator->firstItem() }}</span>
{!! __('to') !!}
<span class="font-medium">{{ $paginator->lastItem() }}</span>
@else
{{ $paginator->count() }}
@endif
{!! __('of') !!}
<span class="font-medium">{{ $paginator->total() }}</span>
{!! __('results') !!}
</p>
</div>
<div>
<span class="relative z-0 inline-flex shadow-sm rounded-md">
{{-- Previous Page Link --}}
@if ($paginator->onFirstPage())
{{-- <span aria-disabled="true" aria-label="{{ __('pagination.previous') }}">
<span class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-gray-500 bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md cursor-default rounded-l-md leading-5" aria-hidden="true">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
</span>
</span> --}}
@else
<a href="{{ $paginator->previousPageUrl() }}" rel="prev"
class="relative inline-flex items-center px-2 py-2 text-sm font-medium text-white bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md rounded-l-md leading-5focus:z-10 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
aria-label="{{ __('pagination.previous') }}">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z"
clip-rule="evenodd" />
</svg>
</a>
@endif
{{-- Pagination Elements --}}
@foreach ($elements as $element)
{{-- "Three Dots" Separator --}}
@if (is_string($element))
<span aria-disabled="true">
<span
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-white bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md cursor-default leading-5">{{ $element }}</span>
</span>
@endif
{{-- Array Of Links --}}
@if (is_array($element))
@foreach ($element as $page => $url)
@if ($page == $paginator->currentPage())
<span aria-current="page">
<span
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-white bg-main-primary hover:bg-opacity-80 shadow-md cursor-default leading-5">{{ $page }}</span>
</span>
@else
<a href="{{ $url }}"
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-white bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md leading-5 focus:z-10 active:bg-gray-100 active:text-white transition ease-in-out duration-150"
aria-label="{{ __('Go to page :page', ['page' => $page]) }}">
{{ $page }}
</a>
@endif
@endforeach
@endif
@endforeach
{{-- Next Page Link --}}
@if ($paginator->hasMorePages())
<a href="{{ $paginator->nextPageUrl() }}" rel="next"
class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-white bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md rounded-r-md leading-5 focus:z-10 active:bg-gray-100 active:text-gray-500 transition ease-in-out duration-150"
aria-label="{{ __('pagination.next') }}">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd" />
</svg>
</a>
@else
{{-- <span aria-disabled="true" aria-label="{{ __('pagination.next') }}">
<span class="relative inline-flex items-center px-2 py-2 -ml-px text-sm font-medium text-gray-500 bg-main-800/40 hover:bg-main-primary hover:shadow-menu shadow-md cursor-default rounded-r-md leading-5" aria-hidden="true">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
</svg>
</span>
</span> --}}
@endif
</span>
</div>
</div>
</nav>
@endif

View File

@@ -0,0 +1,31 @@
<div class="grid grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-5 grid-flow-row gap-2">
@foreach ($item['data'] ?? [] as $movie)
@if ($loop->first)
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name ?? '' }}"
class="row-span-1 col-span-2 md:row-span-2 md:col-span-2 overflow-hidden relative group">
<img
src="{{ $movie->getPosterUrl() }}" alt="{{ $movie->name ?? '' }}"
class="group-hover:opacity-60 transition-all duration-500 transform group-hover:scale-110 w-full h-full" />
<span class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">{{ $movie->name }}</div>
<div class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<span class="absolute top-0 left-0 p-0.5 bg-main-primary text-sm">{{ $movie->episode_current }}</span>
</a>
@else
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name ?? '' }}" class="@if ($loop->last) hidden lg:block @endif overflow-hidden relative group">
<img data-src="{{ $movie->getPosterUrl() }}" alt="{{ $movie->name ?? '' }}"
class="group-hover:opacity-60 transition-all duration-500 transform group-hover:scale-110 w-full h-full lazyload" />
<span class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">{{ $movie->name }}</div>
<div class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<span class="absolute top-0 left-0 p-0.5 bg-main-primary text-sm">{{ $movie->episode_current }}</span>
</a>
@endif
@endforeach
</div>

View File

@@ -0,0 +1,27 @@
<div
class="grid grid-flow-row grid-cols-2 sm:grid-cols-3 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-6 gap-2">
@foreach ($item['data'] ?? [] as $movie)
<a href="{{ $movie->getUrl() }}" class="overflow-hidden relative group" title="{{ $movie->name ?? '' }}">
<img style="aspect-ratio: 256/340" data-src="{{ $movie->getThumbUrl() }}" alt="{{ $movie->name ?? '' }}"
class="w-full group-hover:opacity-60 transition-all duration-500 transform group-hover:scale-110 lazyload" />
<span class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">{{ $movie->name }}</div>
<div class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<span class="absolute top-0 left-0 p-0.5 bg-main-primary text-sm">
{{ $movie->episode_current }}
</span>
<div class="absolute hidden top-1/3 left-1/3 animate-pulse group-hover:block">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-16 h-16 text-main-warning">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.91 11.672a.375.375 0 010 .656l-5.603 3.113a.375.375 0 01-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112z" />
</svg>
</div>
</a>
@endforeach
</div>

View File

@@ -0,0 +1,67 @@
<div id="slider_poster_{{ $loop->index }}" class="owl-carousel owl-theme absolute">
@foreach ($item['data'] ?? [] as $movie)
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name ?? '' }}"
class="block row-span-1 col-span-1 md:row-span-2 md:col-span-2 overflow-hidden relative group">
<img style="aspect-ratio: 16/9" src="{{ $movie->getPosterUrl() }}" alt="{{ $movie->name ?? '' }}"
class="group-hover:opacity-60 transition-all duration-500 transform group-hover:bg-opacity-60" />
<span class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">{{ $movie->name }}</div>
<div class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<span class="absolute top-0 left-0 p-0.5 bg-main-primary text-sm">{{ $movie->episode_current }}</span>
</a>
@endforeach
</div>
@push('scripts')
<script>
$(document).ready(function() {
$("#slider_poster_{{ $loop->index }}").owlCarousel({
items: 1,
center: false,
loop: true,
dots: false,
nav: true,
margin: 10,
stagePadding: 0,
stageOuterClass: 'owl-stage-outer',
responsive: {
1400: {
items: 4
},
1280: {
items: 3
},
1024: {
items: 2
},
768: {
items: 2
},
},
scrollPerPage: true,
lazyLoad: true,
slideSpeed: 800,
paginationSpeed: 400,
stopOnHover: true,
autoplay: true,
navText: [
`<button class="block absolute top-0 left-0 text-white bg-main-900/60 h-[90%]">
<span style="display: none" aria-label="Previous">‹</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 px-1 top-0 text-2xl">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5" />
</svg>
</button>`,
`<button class="block absolute top-0 right-0 text-white bg-main-900/60 h-[90%]">
<span style="display: none" aria-label="Next">›</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 px-1 top-0 text-2xl">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5" />
</svg>
</button>`
],
});
});
</script>
@endpush

View File

@@ -0,0 +1,67 @@
<div id="slider_thumb_{{ $loop->index }}" class="owl-carousel owl-theme absolute">
@foreach ($item['data'] ?? [] as $movie)
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name ?? '' }}"
class="block row-span-1 col-span-1 md:row-span-2 md:col-span-2 overflow-hidden relative group">
<img style="aspect-ratio: 256/340" src="{{ $movie->getThumbUrl() }}" alt="{{ $movie->name ?? '' }}"
class="group-hover:opacity-60 transition-all duration-500 transform group-hover:bg-opacity-60" />
<span class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">{{ $movie->name }}</div>
<div class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<span class="absolute top-0 left-0 p-0.5 bg-main-primary text-sm">{{ $movie->episode_current }}</span>
</a>
@endforeach
</div>
@push('scripts')
<script>
$(document).ready(function() {
$("#slider_thumb_{{ $loop->index }}").owlCarousel({
items: 2,
center: false,
loop: true,
dots: false,
nav: true,
margin: 10,
stagePadding: 0,
stageOuterClass: 'owl-stage-outer',
responsive: {
1400: {
items: 6
},
1280: {
items: 4
},
1024: {
items: 3
},
768: {
items: 2
},
},
scrollPerPage: true,
lazyLoad: true,
slideSpeed: 800,
paginationSpeed: 400,
stopOnHover: true,
autoplay: true,
navText: [
`<button class="block absolute top-0 left-0 text-white bg-main-900/60 h-[90%]">
<span style="display: none" aria-label="Previous">‹</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 px-1 top-0 text-2xl">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5" />
</svg>
</button>`,
`<button class="block absolute top-0 right-0 text-white bg-main-900/60 h-[90%]">
<span style="display: none" aria-label="Next">›</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 px-1 top-0 text-2xl">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5" />
</svg>
</button>`
],
});
});
</script>
@endpush

View File

@@ -0,0 +1,165 @@
@extends('themes::september.layout')
@php
use Ophim\Core\Models\Movie;
$recommendations = Cache::remember('site.movies.recommendations', setting('site_cache_ttl', 5 * 60), function () {
return Movie::where('is_recommended', true)
->limit(get_theme_option('recommendations_limit', 10))
->orderBy('updated_at', 'desc')
->get();
});
$data = Cache::remember('site.movies.latest', setting('site_cache_ttl', 5 * 60), function () {
$lists = preg_split('/[\n\r]+/', get_theme_option('latest'));
$data = [];
foreach ($lists as $list) {
if (trim($list)) {
$list = explode('|', $list);
[$label, $relation, $field, $val, $limit, $link, $template] = array_merge($list, ['Phim mới cập nhật', '', 'type', 'series', 8, '/', 'section_thumb']);
try {
$data[] = [
'label' => $label,
'template' => $template,
'data' => Movie::when($relation, function ($query) use ($relation, $field, $val) {
$query->whereHas($relation, function ($rel) use ($field, $val) {
$rel->where($field, $val);
});
})
->when(!$relation, function ($query) use ($field, $val) {
$query->where($field, $val);
})
->limit($limit)
->orderBy('updated_at', 'desc')
->get(),
'link' => $link ?: '#',
];
} catch (\Exception $e) {
}
}
}
return $data;
});
@endphp
@section('slider_recommended')
@if (count($recommendations))
<div class="container mx-auto px-2 md:px-0">
<section class="w-full mt-2">
<div class="flex justify-between">
<div class="py-2 w-max">
<h3
class="text-base md:text-2xl uppercase font-semibold text-transparent bg-clip-text bg-gradient-to-r from-[#7367F0] to-[#8e84fc]">
Phim HOT</h3>
</div>
</div>
<div id="slider_recommend" class="owl-carousel owl-theme absolute">
@foreach ($recommendations ?? [] as $movie)
<a href="{{ $movie->getUrl() }}" title="{{ $movie->name ?? '' }}"
class="block row-span-1 col-span-1 md:row-span-2 md:col-span-2 overflow-hidden relative group">
<img style="aspect-ratio: 256/340" src="{{ $movie->getThumbUrl() }}" alt="{{ $movie->name ?? '' }}"
class="group-hover:opacity-60 transition-all duration-500 transform group-hover:bg-opacity-60" />
<span
class="absolute bottom-0 px-2 pb-2 pt-16 bg-gradient-to-t from-main-900 w-full text-main-warning">
<div class="font-bold overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->name }}
</div>
<div
class="text-sm text-gray-300 italic overflow-hidden overflow-ellipsis whitespace-nowrap">
{{ $movie->origin_name }} ({{ $movie->publish_year }})
</div>
</span>
<span
class="absolute top-0 left-0 p-0.5 bg-main-primary text-sm">{{ $movie->episode_current }}</span>
</a>
@endforeach
</div>
</section>
</div>
@endif
@endsection
@section('content')
<h1 class="h-text text-white uppercase mb-2">{{ $title }}</h1>
@foreach ($data as $item)
<section class="w-full mt-2">
<div class="flex justify-between">
<div class="py-2 w-max">
<h3
class="text-base md:text-2xl uppercase font-semibold text-transparent bg-clip-text bg-gradient-to-r from-[#7367F0] to-[#8e84fc]">
{{ $item['label'] }}</h3>
<div class="w-full h-0.5 bg-main-indigo bg-gradient-to-r from-[#7367F0] to-[#8e84fc]"></div>
</div>
<a href="{{ $item['link'] }}" title="{{ $item['label'] }}">
<div class="px-2 py-2 flex gap-x-2 text-md text-main-purple hover:text-main-orange items-center">
<span>Xem thêm</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5" />
</svg>
</div>
</a>
</div>
@include('themes::september.inc.section.' . $item['template'])
</section>
@endforeach
@endsection
@section('footer')
@parent
<link rel="stylesheet" href="/themes/september/plugins/owlcarousel/owl.carousel.min.css">
<link rel="stylesheet" href="/themes/september/plugins/owlcarousel/owl.theme.default.min.css">
<script src="/themes/september/plugins/owlcarousel/owl.carousel.min.js"></script>
@if (count($recommendations))
<script>
$(document).ready(function() {
$("#slider_recommend").owlCarousel({
items: 2,
center: false,
loop: true,
dots: false,
nav: true,
margin: 10,
stagePadding: 0,
stageOuterClass: 'owl-stage-outer',
responsive: {
1400: {
items: 8
},
1280: {
items: 6
},
1024: {
items: 4
},
768: {
items: 3
},
},
scrollPerPage: true,
lazyLoad: true,
slideSpeed: 800,
paginationSpeed: 400,
stopOnHover: true,
autoplay: true,
navText: [
`<button class="block absolute top-0 left-0 text-white bg-main-900/60 h-[90%]">
<span style="display: none" aria-label="Previous">‹</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 px-1 top-0 text-2xl">
<path stroke-linecap="round" stroke-linejoin="round" d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5" />
</svg>
</button>`,
`<button class="block absolute top-0 right-0 text-white bg-main-900/60 h-[90%]">
<span style="display: none" aria-label="Next">›</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8 px-1 top-0 text-2xl">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.25 4.5l7.5 7.5-7.5 7.5m-6-15l7.5 7.5-7.5 7.5" />
</svg>
</button>`
],
});
});
</script>
@endif
@endsection

View File

@@ -0,0 +1,90 @@
@extends('themes::layout')
@php
$menu = \VsMov\Core\Models\Menu::getTree();
$tops = Cache::remember('site.movies.tops', setting('site_cache_ttl', 5 * 60), function () {
$lists = preg_split('/[\n\r]+/', get_theme_option('hotest'));
$data = [];
foreach ($lists as $list) {
if (trim($list)) {
$list = explode('|', $list);
[$label, $relation, $field, $val, $sortKey, $alg, $limit, $template] = array_merge($list, ['Phim hot', '', 'type', 'series', 'view_total', 'desc', 4, 'top_thumb']);
try {
$data[] = [
'label' => $label,
'template' => $template,
'data' => \VsMov\Core\Models\Movie::when($relation, function ($query) use ($relation, $field, $val) {
$query->whereHas($relation, function ($rel) use ($field, $val) {
$rel->where($field, $val);
});
})
->when(!$relation, function ($query) use ($field, $val) {
$query->where($field, $val);
})
->orderBy($sortKey, $alg)
->limit($limit)
->get(),
];
} catch (\Exception $e) {
# code
}
}
}
return $data;
});
@endphp
@push('header')
<link href="/themes/september/css/styles.min.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,300;0,400;0,500;0,600;1,400;1,500;1,600" rel="stylesheet" />
@endpush
@section('body')
@include('themes::september.inc.header')
@include('themes::september.inc.nav')
@if (get_theme_option('ads_header'))
<div class="container mx-auto items-center text-center mt-2">
{!! get_theme_option('ads_header') !!}
</div>
@endif
@yield('slider_recommended')
@yield('breadcrumb')
@yield('player_wraper')
<div class="container mx-auto lg:flex px-2 md:px-0">
<div class="lg:w-3/4 lg:pr-4">
@yield('content')
</div>
<aside class="lg:w-1/4">
@include('themes::september.inc.aside')
</aside>
</div>
@endsection
@section('footer')
<footer class="bg-main-800 mt-2 relative bottom-0 clear-both">
<div class="container mx-auto flex py-8">
{!! get_theme_option('footer') !!}
</div>
</footer>
@if (get_theme_option('ads_header'))
<div class="relative">
<div class="container mx-auto px-4 md:px-8 xl:px-40 fixed items-center text-center bottom-0 right-0 left-0 z-40">
{!! get_theme_option('ads_catfish') !!}
</div>
</div>
@endif
<script src="/themes/september/js/jquery-3.6.0.min.js"></script>
<script>
$(document).ready(function() {
$("#header-search-button").click(() => {
$("#header-search-form").toggle("fast", "swing");
})
$("#header-menu-button").click(() => {
$("#nav-menu").toggle("fast", "swing");
})
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.3.2/lazysizes.min.js" integrity="sha512-q583ppKrCRc7N5O0n2nzUiJ+suUv7Et1JGels4bXOaMFQcamPk9HjdUknZuuFjBNs7tsMuadge5k9RzdmO+1GQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
{!! setting('site_scripts_google_analytics') !!}
@endsection

View File

@@ -0,0 +1,385 @@
@extends('themes::september.layout')
@section('breadcrumb')
<div class="breadcrumb block md:flex items-center container mx-auto mt-2 w-full line-clamp-1">
<div class="ml-2 md:ml-0">
<ol class="flex flex-wrap items-center gap-1" itemScope itemType="https://schema.org/BreadcrumbList">
<li itemProp="itemListElement" itemScope itemType="http://schema.org/ListItem">
<a class="flex items-center gap-x-1" itemProp="item" title="Xem phim" href="/">
<span class="flex items-center gap-x-1 text-main-primary hover:text-main-warning" itemProp="name">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.25 21v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21m0 0h4.5V3.545M12.75 21h7.5V10.75M2.25 21h1.5m18 0h-18M2.25 9l4.5-1.636M18.75 3l-1.5.545m0 6.205l3 1m1.5.5l-1.5-.5M6.75 7.364V3h-3v18m3-13.636l10.5-3.819" />
</svg>
Xem phim
</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemProp="position" content="1" />
</a>
</li>
@foreach ($currentMovie->regions as $region)
<li itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a class="text-main-primary hover:text-main-warning" itemprop="item" href="{{ $region->getUrl() }}"
title="{{ $region->name }}">
<span itemprop="name">
{{ $region->name }}
</span>
</a>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemprop="position" content="3">
</li>
@endforeach
@foreach ($currentMovie->categories as $category)
<li itemprop="itemListElement" itemscope="" itemtype="http://schema.org/ListItem">
<a class="text-main-primary hover:text-main-warning" itemprop="item"
href="{{ $category->getUrl() }}" title="{{ $category->name }}">
<span itemprop="name">
{{ $category->name }}
</span>
</a>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
<meta itemprop="position" content="3">
</li>
@endforeach
<li class="inline text-gray-400" itemprop="itemListElement" itemscope=""
itemtype="http://schema.org/ListItem">
<a itemprop="item" href="{{ $currentMovie->getUrl() }}" title="{{ $currentMovie->name }}">
<span itemprop="name">
{{ $currentMovie->name }}
</span>
</a>
<meta itemprop="position" content="4">
</li>
</ol>
</div>
</div>
@endsection
@section('content')
@if ($currentMovie->showtimes && $currentMovie->showtimes != '')
<div class="mt-2.5 p-2 bg-main-800 mb-1 border-main-warning/60 border-[1px] border-dashed text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 animate-pulse inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5m-9-6h.008v.008H12v-.008zM12 15h.008v.008H12V15zm0 2.25h.008v.008H12v-.008zM9.75 15h.008v.008H9.75V15zm0 2.25h.008v.008H9.75v-.008zM7.5 15h.008v.008H7.5V15zm0 2.25h.008v.008H7.5v-.008zm6.75-4.5h.008v.008h-.008v-.008zm0 2.25h.008v.008h-.008V15zm0 2.25h.008v.008h-.008v-.008zm2.25-4.5h.008v.008H16.5v-.008zm0 2.25h.008v.008H16.5V15z" />
</svg>
Lịch chiếu: <span class="text-yellow-500">{!! $currentMovie->showtimes !!}</span>
</div>
@endif
@if ($currentMovie->notify && $currentMovie->notify != '')
<div class="mt-2.5 p-2 bg-main-800 mb-1 border-main-warning/60 border-[1px] border-dashed text-gray-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 animate-pulse inline">
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5" />
</svg>
Thông báo: <span class="text-main-blue">{{ strip_tags($currentMovie->notify) }}</span>
</div>
@endif
<div class="bg-main-800/40 p-2 mt-2 md:flex flex-wrap">
<div class="relative overflow-hidden w-full h-full pt-0 md:w-1/3 lg:w-1/3 xl:w-1/4">
<img style="aspect-ratio: 256/340" src="{{ $currentMovie->getThumbUrl() }}"
alt="{{ $currentMovie->name }} - {{ $currentMovie->origin_name }} ({{ $currentMovie->publish_year }})"
class="w-full h-auto max-h-96" />
@if ($currentMovie->is_copyright)
<div
class="absolute top-[7%] -left-[34%] text-white uppercase py-[4px] px-0 text-[12px] w-full text-center -rotate-45 bg-gradient-to-r from-red-500">
bản quyền</div>
@endif
<div class="absolute top-0 right-0 bg-main-800/80 p-2 flex items-center gap-x-1 text-main-success">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
<span class="text-xs">
{{ $currentMovie->view_total }}
</span>
</div>
<div class="absolute bottom-4 text-center w-full bg-main-700 bg-opacity-40 py-2 m-0">
@if ($currentMovie->trailer_url)
<label id="toggleModal-trailer"
class="bg-main-blue text-gray-50 inline-block px-3 py-2 shadow-none hover:shadow-secondary duration-150 cursor-pointer">
Trailer
</label>
@endif
{{-- optimize sau --}}
@if (!$currentMovie->is_copyright && count($currentMovie->episodes) && $currentMovie->episodes[0]['link'] != '')
<a href="{{ $currentMovie->episodes->sortBy([['server', 'asc']])->groupBy('server')->first()->sortByDesc('name', SORT_NATURAL)->groupBy('name')->last()->sortByDesc('type')->first()->getUrl() }}">
<div
class="bg-main-primary text-gray-50 inline-block px-3 py-2 shadow-none hover:shadow-primary duration-150">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="w-6 h-6 inline">
<path stroke-linecap="round" stroke-linejoin="round"
d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
<path stroke-linecap="round" stroke-linejoin="round"
d="M15.91 11.672a.375.375 0 010 .656l-5.603 3.113a.375.375 0 01-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112z" />
</svg>
Xem phim
</div>
</a>
@else
<div class="text-white">Đang cập nhật...</div>
@endif
</div>
</div>
@if ($currentMovie->trailer_url)
@php
parse_str(parse_url($currentMovie->trailer_url, PHP_URL_QUERY), $parse_url);
$trailer_id = $parse_url['v'];
@endphp
<div id="modal-trailer" class="relative z-10 hidden" aria-labelledby="modal-title" role="dialog"
aria-modal="true">
<div class="fixed inset-0 bg-black bg-opacity-75 transition-opacity"></div>
<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<div id="modal-trailer-iframe"
class="relative transform overflow-hidden text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg">
<div class=" bg-main-800/60 p-2">
<iframe width="100%" height="315"
src="https://www.youtube.com/embed/{{ $trailer_id }}" title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
frameborder="0" scrolling="no" allowfullscreen></iframe>
</div>
<div class="bg-main-900/60 px-4 py-3 sm:flex sm:flex-row-reverse sm:px-6">
<button type="button" id="close-trailer-modal"
class="mt-3 inline-flex w-full justify-center border border-gray-300 bg-white px-2 py-1 text-base font-medium text-gray-700 shadow-sm hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">Đóng</button>
</div>
</div>
</div>
</div>
</div>
@endif
<div class="w-full md:w-2/3 lg:w-2/3 xl:w-3/4 md:pl-2 pt-0 md:mt-0 text-sm">
<div class="bg-main-700/40 text-center py-2">
<h1 class="uppercase text-xl font-extrabold text-main-primary">{{ $currentMovie->name }}</h1>
<h2 class="italic text-main-orange">{{ $currentMovie->origin_name ?? '' }}</h2>
<div class="mt-2">
<div class="items-center text-center gap-x-2">
<div id="movies-rating-star" class="flex justify-center" style="height: 18px;"></div>
<div class="text-white align-middle">
({{ $currentMovie->getRatingStar() }}
sao
/
{{$currentMovie->getRatingCount()}} đánh giá)
</div>
<div id="movies-rating-msg" class="text-[#FDB813] mb-2 font-bold text-sm mt-2"></div>
</div>
</div>
</div>
<ul class="grid grid-flow-row grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 mt-2 gap-2">
<li>
<label class="font-bold text-white">Trạng thái: </label>
<span class="px-1 bg-main-labelbgWarning text-main-warning">{{ $currentMovie->getStatus() }}</span>
</li>
<li>
<label class="font-bold text-white">Tập hiện tại: </label>
<span class="px-1 bg-main-labelbgSuccess text-main-success">{{ $currentMovie->episode_current }} {{ $currentMovie->language }}</span>
</li>
<li>
<label class="font-bold text-white">Số tập: </label>
<span class="px-1 bg-main-labelbgInfo text-main-info">
{{ $currentMovie->episode_total ?? 'N/A' }}
</span>
</li>
<li>
<label class="font-bold text-white">Thời lượng: </label>
<span class="">{{ $currentMovie->episode_time ?? 'N/A' }}</span>
</li>
<li>
<label class="font-bold text-white">Năm phát hành: </label>
<span class="">{{ $currentMovie->publish_year }}</span>
</li>
<li>
<label class="font-bold text-white">Chất lượng: </label>
<span class="">{{ $currentMovie->quality }}</span>
</li>
<li class="col-span-1 md:col-span-2 lg:col-span-2 xl:col-span-3 line-clamp-2">
<label class="font-bold text-white">Thể loại: </label>
<span class="">
{!! $currentMovie->categories->map(function ($category) {
return '<a href="' .
$category->getUrl() .
'" title="' .
$category->name .
'" class="text-main-primary hover:text-main-orange">' .
$category->name .
'</a>';
})->implode(', ') !!}
</span>
</li>
<li class="col-span-1 md:col-span-2 lg:col-span-2 xl:col-span-3 line-clamp-2">
<label class="font-bold text-white">Quốc gia: </label>
<span class="">
{!! $currentMovie->regions->map(function ($region) {
return '<a href="' .
$region->getUrl() .
'" title="' .
$region->name .
'" class="text-main-primary hover:text-main-orange">' .
$region->name .
'</a>';
})->implode(', ') !!}
</span>
</li>
<li class="col-span-1 md:col-span-2 lg:col-span-2 xl:col-span-3 line-clamp-2">
<label class="font-bold text-white">Diễn viên: </label>
<span class="">
{!! $currentMovie->actors->map(function ($actor) {
return '<a href="' .
$actor->getUrl() .
'" tite="Diễn viên ' .
$actor->name .
'" class="text-main-primary hover:text-main-orange">' .
$actor->name .
'</a>';
})->implode(', ') !!}
</span>
</li>
<li class="col-span-1 md:col-span-2 lg:col-span-2 xl:col-span-3 line-clamp-2">
<label class="font-bold text-white">Đạo diễn: </label>
<span class="">
{!! $currentMovie->directors->map(function ($director) {
return '<a href="' .
$director->getUrl() .
'" tite="Đạo diễn ' .
$director->name .
'" class="text-main-primary hover:text-main-orange">' .
$director->name .
'</a>';
})->implode(', ') !!}
</span>
</li>
@if ($currentMovie->type === "series" && !$currentMovie->is_copyright && count($currentMovie->episodes) && $currentMovie->episodes[0]['link'] != '')
<li class="col-span-1 md:col-span-2 lg:col-span-2 xl:col-span-3">
<label class="font-bold text-white">Tập mới nhất: </label>
<span class="flex gap-1">
@php
$currentMovie->episodes
->sortBy([['name', 'desc'], ['type', 'desc']])
->sortByDesc('name', SORT_NATURAL)
->unique('name')
->take(3)
->map(function ($episode) use ($currentMovie) {
echo '<a href="' . $episode->getUrl() . '" title="'. $currentMovie->name .' tập '. $episode->name .'" class="w-content text-center bg-main-labelbgSecondary text-white hover:shadow-menu hover:bg-main-primary duration-150 px-4 py-1 overflow-hidden overflow-ellipsis whitespace-nowrap">' . $episode->name . '</a>';
});
@endphp
</span>
</li>
@endif
</ul>
</div>
</div>
<article class="block bg-main-800/40 p-2 mt-2">
<h3 class="font-medium text-xl pb-2">Nội dung phim</h3>
<div class="border-b-[1px] border-main-secondary border-opacity-25 py-1">
@if ($currentMovie->content)
<div class="whitespace-pre-wrap">{!! $currentMovie->content !!}</div>
@else
<p>Đang cập nhật ...</p>
@endif
</div>
<div>
<h3 class="font-medium text-xl py-2">Tags</h3>
<ul class="flex flex-wrap gap-1 py-1">
@foreach ($currentMovie->tags as $tag)
<li class="bg-main-labelbgPrimary text-white hover:text-main-warning px-2">
<a href="{{ $tag->getUrl() }}" title="{{ $tag->name }}">{{ $tag->name }}</a>
</li>
@endforeach
</ul>
</div>
</article>
<div class="bg-white mt-2 w-full" style="background-color: #ffffff">
<div class="fb-comments w-full" data-href="{{ $currentMovie->getUrl() }}" data-width="100%"
data-numposts="5" data-colorscheme="light" data-lazy="true">
</div>
</div>
<section class="w-full mt-2">
<div class="flex justify-between">
<div class="py-2 w-max">
<h3
class="text-base md:text-2xl uppercase font-semibold text-transparent bg-clip-text bg-gradient-to-r from-[#7367F0] to-[#8e84fc]">
Có thể bạn muốn xem</h3>
</div>
</div>
@php
$item['data'] = $movie_related;
@endphp
@include('themes::september.inc.section.section_thumb')
</section>
@endsection
@push('scripts')
<script type="text/javascript">
$("#toggleModal-trailer").click(() => {
$("#modal-trailer").toggle("hidden");
})
$('body').click(function(event) {
if (($(event.target).closest("#modal-trailer").length && !$(event.target).closest(
"#modal-trailer-iframe").length) || $(event.target).closest("#close-trailer-modal").length) {
$("#modal-trailer").toggle("hidden");
}
});
</script>
<script src="/themes/september/plugins/jquery-raty/jquery.raty.js"></script>
<link href="/themes/september/plugins/jquery-raty/jquery.raty.css" rel="stylesheet" type="text/css" />
<script>
var rated = false;
$('#movies-rating-star').raty({
score: {{ $currentMovie->getRatingStar() }},
number: 10,
numberMax: 10,
hints: ['quá tệ', 'tệ', 'không hay', 'không hay lắm', 'bình thường', 'xem được', 'có vẻ hay', 'hay',
'rất hay', 'siêu phẩm'
],
starOff: '/themes/september/plugins/jquery-raty/images/star-off.png',
starOn: '/themes/september/plugins/jquery-raty/images/star-on.png',
starHalf: '/themes/september/plugins/jquery-raty/images/star-half.png',
click: function(score, evt) {
if (rated) return
fetch("{{ route('movie.rating', ['movie' => $currentMovie->slug]) }}", {
method: 'POST',
headers: {
"Content-Type": "application/json",
'X-CSRF-TOKEN': document.querySelector(
'meta[name="csrf-token"]')
.getAttribute(
'content')
},
body: JSON.stringify({
rating: score
})
});
rated = true;
$('#movies-rating-star').data('raty').readOnly(true);
$('#movies-rating-msg').html(`Bạn đã đánh giá ${score} sao cho phim này!`);
}
});
</script>
{!! setting('site_scripts_facebook_sdk') !!}
@endpush

54
routes/web.php Executable file
View File

@@ -0,0 +1,54 @@
<?php
use Illuminate\Support\Facades\Route;
use VsMov\September\Controllers\SeptemberController;
// --------------------------
// Custom Backpack Routes
// --------------------------
// This route file is loaded automatically by Backpack\Base.
// Routes you generate using Backpack\Generators will be placed here.
Route::group([
'middleware' => array_merge(
(array) config('backpack.base.web_middleware', 'web'),
),
], function () {
Route::get('/', [SeptemberController::class, 'index']);
Route::get(setting('site_routes_category', '/the-loai/{category}'), [SeptemberController::class, 'getMovieOfCategory'])
->where(['category' => '.+', 'id' => '[0-9]+'])
->name('categories.movies.index');
Route::get(setting('site_routes_region', '/quoc-gia/{region}'), [SeptemberController::class, 'getMovieOfRegion'])
->where(['region' => '.+', 'id' => '[0-9]+'])
->name('regions.movies.index');
Route::get(setting('site_routes_tag', '/tu-khoa/{tag}'), [SeptemberController::class, 'getMovieOfTag'])
->where(['tag' => '.+', 'id' => '[0-9]+'])
->name('tags.movies.index');
Route::get(setting('site_routes_types', '/danh-sach/{type}'), [SeptemberController::class, 'getMovieOfType'])
->where(['type' => '.+', 'id' => '[0-9]+'])
->name('types.movies.index');
Route::get(setting('site_routes_actors', '/dien-vien/{actor}'), [SeptemberController::class, 'getMovieOfActor'])
->where(['actor' => '.+', 'id' => '[0-9]+'])
->name('actors.movies.index');
Route::get(setting('site_routes_directors', '/dao-dien/{director}'), [SeptemberController::class, 'getMovieOfDirector'])
->where(['director' => '.+', 'id' => '[0-9]+'])
->name('directors.movies.index');
Route::get(setting('site_routes_episode', '/phim/{movie}/{episode}-{id}'), [SeptemberController::class, 'getEpisode'])
->where(['movie' => '.+', 'movie_id' => '[0-9]+', 'episode' => '.+', 'id' => '[0-9]+'])
->name('episodes.show');
Route::post(sprintf('/%s/{movie}/{episode}-{id}/report', config('vsmov.routes.movie', 'phim')), [SeptemberController::class, 'reportEpisode'])
->where(['movie' => '.+', 'episode' => '.+', 'id' => '[0-9]+'])->name('episodes.report');
Route::post(sprintf('/%s/{movie}/rate', config('vsmov.routes.movie', 'phim')), [SeptemberController::class, 'rateMovie'])->name('movie.rating');
Route::get(setting('site_routes_movie', '/phim/{movie}'), [SeptemberController::class, 'getMovieOverview'])
->where(['movie' => '.+', 'id' => '[0-9]+'])
->name('movies.show');
});

View File

@@ -0,0 +1,289 @@
<?php
namespace VsMov\September\Controllers;
use Backpack\Settings\app\Models\Setting;
use Illuminate\Http\Request;
use VsMov\Core\Models\Actor;
use VsMov\Core\Models\Catalog;
use VsMov\Core\Models\Category;
use VsMov\Core\Models\Director;
use VsMov\Core\Models\Episode;
use VsMov\Core\Models\Movie;
use VsMov\Core\Models\Region;
use VsMov\Core\Models\Tag;
use Illuminate\Support\Facades\Cache;
class SeptemberController
{
public function index(Request $request)
{
if ($request['search'] || $request['filter']) {
$data = Movie::when(!empty($request['filter']['category']), function ($movie) {
$movie->whereHas('categories', function ($categories) {
$categories->where('id', request('filter')['category']);
});
})->when(!empty($request['filter']['region']), function ($movie) {
$movie->whereHas('regions', function ($regions) {
$regions->where('id', request('filter')['region']);
});
})->when(!empty($request['filter']['year']), function ($movie) {
$movie->where('publish_year', request('filter')['year']);
})->when(!empty($request['filter']['type']), function ($movie) {
$movie->where('type', request('filter')['type']);
})->when(!empty($request['search']), function ($query, $movie) {
$query->where(function ($query) {
$query->where('name', 'like', '%' . request('search') . '%')
->orWhere('origin_name', 'like', '%' . request('search') . '%');
})->orderBy('name', 'desc');
})->when(!empty($request['filter']['sort']), function ($movie) {
if (request('filter')['sort'] == 'create') {
return $movie->orderBy('created_at', 'desc');
}
if (request('filter')['sort'] == 'update') {
return $movie->orderBy('updated_at', 'desc');
}
if (request('filter')['sort'] == 'year') {
return $movie->orderBy('publish_year', 'desc');
}
if (request('filter')['sort'] == 'view') {
return $movie->orderBy('view_total', 'desc');
}
})->paginate(get_theme_option('per_page_limit'));
return view('themes::september.catalog', [
'data' => $data,
'search' => $request['search'],
'section_name' => "Tìm kiếm phim: $request->search"
]);
}
return view('themes::september.index', [
'title' => Setting::get('site_homepage_title')
]);
}
public function getMovieOverview(Request $request)
{
/** @var Movie */
$movie = Movie::fromCache()->find($request->movie ?: $request->id);
if (is_null($movie)) abort(404);
$movie->generateSeoTags();
$movie->increment('view_total', 1);
$movie->increment('view_day', 1);
$movie->increment('view_week', 1);
$movie->increment('view_month', 1);
$movie_related_cache_key = 'movie_related.' . $movie->id;
$movie_related = Cache::get($movie_related_cache_key);
if(is_null($movie_related)) {
$movie_related = $movie->categories[0]->movies()->inRandomOrder()->limit(get_theme_option('movie_related_limit'))->get();
Cache::put($movie_related_cache_key, $movie_related, setting('site_cache_ttl', 5 * 60));
}
return view('themes::september.single', [
'currentMovie' => $movie,
'title' => $movie->getTitle(),
'movie_related' => $movie_related
]);
}
public function getEpisode(Request $request)
{
$movie = Movie::fromCache()->find($request->movie ?: $request->movie_id)->load('episodes');
if (is_null($movie)) abort(404);
/** @var Episode */
$episode_id = $request->id;
$episode = $movie->episodes->when($episode_id, function ($collection, $episode_id) {
return $collection->where('id', $episode_id);
})->firstWhere('slug', $request->episode);
if (is_null($episode)) abort(404);
$episode->generateSeoTags();
$movie->increment('view_total', 1);
$movie->increment('view_day', 1);
$movie->increment('view_week', 1);
$movie->increment('view_month', 1);
$movie_related_cache_key = 'movie_related.' . $movie->id;
$movie_related = Cache::get($movie_related_cache_key);
if(is_null($movie_related)) {
$movie_related = $movie->categories[0]->movies()->inRandomOrder()->limit(get_theme_option('movie_related_limit'))->get();
Cache::put($movie_related_cache_key, $movie_related, setting('site_cache_ttl', 5 * 60));
}
return view('themes::september.episode', [
'currentMovie' => $movie,
'movie_related' => $movie_related,
'episode' => $episode,
'title' => $episode->getTitle()
]);
}
public function reportEpisode(Request $request, $movie, $slug, $id)
{
$movie = Movie::fromCache()->find($movie)->load('episodes');
$episode = $movie->episodes->when($id, function ($collection, $id) {
return $collection->where('id', $id);
})->firstWhere('slug', $slug);
$episode->update([
'report_message' => request('message', ''),
'has_report' => true
]);
return response([], 204);
}
public function rateMovie(Request $request, $movie)
{
$movie = Movie::fromCache()->find($movie);
$movie->refresh()->increment('rating_count', 1, [
'rating_star' => $movie->rating_star + ((int) request('rating') - $movie->rating_star) / ($movie->rating_count + 1)
]);
return response([], 204);
}
public function getMovieOfCategory(Request $request)
{
/** @var Category */
$category = Category::fromCache()->find($request->category ?: $request->id);
if (is_null($category)) abort(404);
$category->generateSeoTags();
$movies = $category->movies()->orderBy('created_at', 'desc')->paginate(get_theme_option('per_page_limit'));
return view('themes::september.catalog', [
'data' => $movies,
'category' => $category,
'title' => $category->seo_title ?: $category->getTitle(),
'section_name' => "Phim thể loại $category->name"
]);
}
public function getMovieOfRegion(Request $request)
{
/** @var Region */
$region = Region::fromCache()->find($request->region ?: $request->id);
if (is_null($region)) abort(404);
$region->generateSeoTags();
$movies = $region->movies()->orderBy('created_at', 'desc')->paginate(get_theme_option('per_page_limit'));
return view('themes::september.catalog', [
'data' => $movies,
'region' => $region,
'title' => $region->seo_title ?: $region->getTitle(),
'section_name' => "Phim quốc gia $region->name"
]);
}
public function getMovieOfActor(Request $request)
{
/** @var Actor */
$actor = Actor::fromCache()->find($request->actor ?: $request->id);
if (is_null($actor)) abort(404);
$actor->generateSeoTags();
$movies = $actor->movies()->orderBy('created_at', 'desc')->paginate(get_theme_option('per_page_limit'));
return view('themes::september.catalog', [
'data' => $movies,
'person' => $actor,
'title' => $actor->getTitle(),
'section_name' => "Diễn viên $actor->name"
]);
}
public function getMovieOfDirector(Request $request)
{
/** @var Director */
$director = Director::fromCache()->find($request->director ?: $request->id);
if (is_null($director)) abort(404);
$director->generateSeoTags();
$movies = $director->movies()->orderBy('created_at', 'desc')->paginate(get_theme_option('per_page_limit'));
return view('themes::september.catalog', [
'data' => $movies,
'person' => $director,
'title' => $director->getTitle(),
'section_name' => "Đạo diễn $director->name"
]);
}
public function getMovieOfTag(Request $request)
{
/** @var Tag */
$tag = Tag::fromCache()->find($request->tag ?: $request->id);
if (is_null($tag)) abort(404);
$tag->generateSeoTags();
$movies = $tag->movies()->orderBy('created_at', 'desc')->paginate(get_theme_option('per_page_limit'));
return view('themes::september.catalog', [
'data' => $movies,
'tag' => $tag,
'title' => $tag->getTitle(),
'section_name' => "Tags: $tag->name"
]);
}
public function getMovieOfType(Request $request)
{
/** @var Catalog */
$catalog = Catalog::fromCache()->find($request->type ?: $request->id);
if (is_null($catalog)) abort(404);
$catalog->generateSeoTags();
$cache_key = 'catalog.' . $catalog->id . '.page.' . $request['page'];
$movies = Cache::get($cache_key);
if(is_null($movies)) {
$value = explode('|', trim($catalog->value));
[$relation_config, $field, $val, $sortKey, $alg] = array_merge($value, ['', 'is_copyright', 0, 'created_at', 'desc']);
$relation_config = explode(',', $relation_config);
[$relation_table, $relation_field, $relation_val] = array_merge($relation_config, ['', '', '']);
try {
$movies = \VsMov\Core\Models\Movie::when($relation_table, function ($query) use ($relation_table, $relation_field, $relation_val, $field, $val) {
$query->whereHas($relation_table, function ($rel) use ($relation_field, $relation_val, $field, $val) {
$rel->where($relation_field, $relation_val)->where(array_combine(explode(",", $field), explode(",", $val)));
});
})->when(!$relation_table, function ($query) use ($field, $val) {
$query->where(array_combine(explode(",", $field), explode(",", $val)));
})
->orderBy($sortKey, $alg)
->paginate($catalog->paginate);
Cache::put($cache_key, $movies, setting('site_cache_ttl', 5 * 60));
} catch (\Exception $e) {}
}
return view('themes::september.catalog', [
'data' => $movies,
'section_name' => "Danh sách $catalog->name"
]);
}
}

190
src/SeptemberServiceProvider.php Executable file
View File

@@ -0,0 +1,190 @@
<?php
namespace VsMov\September;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class SeptemberServiceProvider extends ServiceProvider
{
public function register()
{
$this->setupDefaultThemeCustomizer();
}
public function boot()
{
$this->loadViewsFrom(__DIR__ . '/../resources/views/', 'themes');
$this->publishes([
__DIR__ . '/../resources/assets' => public_path('themes/september')
], 'september-assets');
}
protected function setupDefaultThemeCustomizer()
{
config(['themes' => array_merge(config('themes', []), [
'september' => [
'name' => 'September',
'author' => 'vsmov@gmail.com',
'package_name' => 'vsmov/vsmov-september',
'publishes' => ['september-assets'],
'preview_image' => '',
'options' => [
[
'name' => 'recommendations_limit',
'label' => 'Recommended movies limit',
'type' => 'number',
'value' => 10,
'wrapperAttributes' => [
'class' => 'form-group col-md-4',
],
'tab' => 'List'
],
[
'name' => 'per_page_limit',
'label' => 'Pages limit',
'type' => 'number',
'value' => 36,
'wrapperAttributes' => [
'class' => 'form-group col-md-4',
],
'tab' => 'List'
],
[
'name' => 'movie_related_limit',
'label' => 'Movies related limit',
'type' => 'number',
'value' => 12,
'wrapperAttributes' => [
'class' => 'form-group col-md-4',
],
'tab' => 'List'
],
[
'name' => 'latest',
'label' => 'Home Page',
'type' => 'code',
'hint' => 'display_label|relation|find_by_field|value|limit|show_more_url|show_template (slider_poster|slider_thumb|section_poster|section_thumb)',
'value' => "Phim bộ mới||type|series|12|/danh-sach/phim-bo|section_thumb\r\nPhim lẻ mới||type|single|12|/danh-sach/phim-bo|section_poster\r\nPhim lẻ mới||type|single|10|/danh-sach/phim-bo|slider_thumb\r\nPhim lẻ mới||type|single|10|/danh-sach/phim-bo|slider_poster",
'attributes' => [
'rows' => 5
],
'tab' => 'List'
],
[
'name' => 'hotest',
'label' => 'Danh sách hot',
'type' => 'code',
'hint' => 'Label|relation|find_by_field|value|sort_by_field|sort_algo|limit|show_template (top_text|top_thumb)',
'value' => "Top phim lẻ||type|single|view_total|desc|9|top_text\r\nTop phim bộ||type|series|view_total|desc|9|top_thumb",
'attributes' => [
'rows' => 5
],
'tab' => 'List'
],
[
'name' => 'additional_css',
'label' => 'Additional CSS',
'type' => 'code',
'value' => "",
'tab' => 'Custom CSS'
],
[
'name' => 'body_attributes',
'label' => 'Body attributes',
'type' => 'text',
'value' => "class='bg-main-900 text-gray-300 font-montserrat leading-normal tracking-normal'",
'tab' => 'Custom CSS'
],
[
'name' => 'additional_header_js',
'label' => 'Header JS',
'type' => 'code',
'value' => "",
'tab' => 'Custom JS'
],
[
'name' => 'additional_body_js',
'label' => 'Body JS',
'type' => 'code',
'value' => "",
'tab' => 'Custom JS'
],
[
'name' => 'additional_footer_js',
'label' => 'Footer JS',
'type' => 'code',
'value' => "",
'tab' => 'Custom JS'
],
[
'name' => 'footer',
'label' => 'Footer',
'type' => 'code',
'value' => <<<EOT
<div class="w-full mx-auto flex flex-wrap">
<div class="flex w-full lg:w-3/5">
<div class="px-2">
<span class="font-bold text-gray-100">Giới Thiệu</span>
<p class="text-gray-300 text-sm pt-3">
<b>VsMov</b> - Website xem phim online miễn phí.
</p>
<p class="text-gray-300 text-sm">Với giao diện trực quan, thuận tiện, tốc độ tải nhanh, ít quảng cáo
hứa
hẹn sẽ đem lại những trải nghiệm tốt cho người xem.</p>
</div>
</div>
<div class="flex w-1/3 lg:w-1/5">
<div class="px-2">
<span class="font-bold text-gray-100">Kết Nối</span>
<ul class="list-reset items-center text-sm pt-3">
<li>
<a class="inline-block text-gray-50 no-underline hover:text-main-primary hover:text-underline py-1"
href="#">
FanPage
</a>
</li>
<li>
<a class="inline-block text-gray-50 no-underline hover:text-main-primary hover:text-underline py-1"
href="#">
Group
</a>
</li>
</ul>
</div>
</div>
<div class="flex w-2/3 lg:w-1/5">
<div class="px-2">
<span class="font-bold text-gray-100">Liên hệ</span>
<ul class="list-reset items-center text-sm pt-3">
<li class="text-gray-300">email@gmail.com</li>
</ul>
</div>
</div>
</div>
EOT,
'tab' => 'Custom HTML'
],
[
'name' => 'ads_header',
'label' => 'Ads header',
'type' => 'code',
'value' => <<<EOT
<img src="" alt="">
EOT,
'tab' => 'Ads'
],
[
'name' => 'ads_catfish',
'label' => 'Ads catfish',
'type' => 'code',
'value' => <<<EOT
<img src="" alt="">
EOT,
'tab' => 'Ads'
]
],
]
])]);
}
}

BIN
src/screenshot.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 615 KiB