Added ability to loop video posts
This commit is contained in:
parent
4d20e1bfc4
commit
93291e5164
16 changed files with 95 additions and 14 deletions
4
TODO
4
TODO
|
@ -2,10 +2,6 @@ This is transient file that lists functionalities to be implemented before
|
||||||
first major release.
|
first major release.
|
||||||
|
|
||||||
everything related to posts:
|
everything related to posts:
|
||||||
- single post view
|
|
||||||
- editing
|
|
||||||
- ability to loop video posts
|
|
||||||
|
|
||||||
- ability to paste many urls in post upload
|
- ability to paste many urls in post upload
|
||||||
|
|
||||||
- post notes
|
- post notes
|
||||||
|
|
|
@ -53,6 +53,7 @@ changePostTags = regularUser, powerUser, moderator, administrator
|
||||||
changePostContent = regularUser, powerUser, moderator, administrator
|
changePostContent = regularUser, powerUser, moderator, administrator
|
||||||
changePostThumbnail = powerUser, moderator, administrator
|
changePostThumbnail = powerUser, moderator, administrator
|
||||||
changePostRelations = regularUser, powerUser, moderator, administrator
|
changePostRelations = regularUser, powerUser, moderator, administrator
|
||||||
|
changePostFlags = regularUser, powerUser, moderator, administrator
|
||||||
|
|
||||||
listTags = anonymous, regularUser, powerUser, moderator, administrator
|
listTags = anonymous, regularUser, powerUser, moderator, administrator
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ App.Auth = function(_, jQuery, util, api, appState, promise) {
|
||||||
changePostContent: 'changePostContent',
|
changePostContent: 'changePostContent',
|
||||||
changePostThumbnail: 'changePostThumbnail',
|
changePostThumbnail: 'changePostThumbnail',
|
||||||
changePostRelations: 'changePostRelations',
|
changePostRelations: 'changePostRelations',
|
||||||
|
changePostFlags: 'changePostFlags',
|
||||||
|
|
||||||
listComments: 'listComments',
|
listComments: 'listComments',
|
||||||
addComments: 'addComments',
|
addComments: 'addComments',
|
||||||
|
|
|
@ -47,6 +47,7 @@ App.Presenters.PostPresenter = function(
|
||||||
editPrivileges.canChangeContent = auth.hasPrivilege(auth.privileges.changePostContent);
|
editPrivileges.canChangeContent = auth.hasPrivilege(auth.privileges.changePostContent);
|
||||||
editPrivileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail);
|
editPrivileges.canChangeThumbnail = auth.hasPrivilege(auth.privileges.changePostThumbnail);
|
||||||
editPrivileges.canChangeRelations = auth.hasPrivilege(auth.privileges.changePostRelations);
|
editPrivileges.canChangeRelations = auth.hasPrivilege(auth.privileges.changePostRelations);
|
||||||
|
editPrivileges.canChangeFlags = auth.hasPrivilege(auth.privileges.changePostFlags);
|
||||||
|
|
||||||
promise.wait(
|
promise.wait(
|
||||||
util.promiseTemplate('post'),
|
util.promiseTemplate('post'),
|
||||||
|
@ -162,6 +163,11 @@ App.Presenters.PostPresenter = function(
|
||||||
attachLinksToPostsAround();
|
attachLinksToPostsAround();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function softRender() {
|
||||||
|
renderSidebar();
|
||||||
|
$el.find('video').prop('loop', post.flags.loop);
|
||||||
|
}
|
||||||
|
|
||||||
function renderSidebar() {
|
function renderSidebar() {
|
||||||
$el.find('#sidebar').html(jQuery(renderPostTemplate()).find('#sidebar').html());
|
$el.find('#sidebar').html(jQuery(renderPostTemplate()).find('#sidebar').html());
|
||||||
attachSidebarEvents();
|
attachSidebarEvents();
|
||||||
|
@ -262,6 +268,7 @@ App.Presenters.PostPresenter = function(
|
||||||
var $form = $el.find('form');
|
var $form = $el.find('form');
|
||||||
var formData = {};
|
var formData = {};
|
||||||
formData.seenEditTime = post.lastEditTime;
|
formData.seenEditTime = post.lastEditTime;
|
||||||
|
formData.flags = {};
|
||||||
|
|
||||||
if (editPrivileges.canChangeContent && postContent) {
|
if (editPrivileges.canChangeContent && postContent) {
|
||||||
formData.content = postContent;
|
formData.content = postContent;
|
||||||
|
@ -287,6 +294,12 @@ App.Presenters.PostPresenter = function(
|
||||||
formData.relations = $form.find('[name=relations]').val();
|
formData.relations = $form.find('[name=relations]').val();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editPrivileges.canChangeFlags) {
|
||||||
|
if (post.contentType === 'video') {
|
||||||
|
formData.flags.loop = $form.find('[name=loop]').is(':checked') ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (post.tags.length === 0) {
|
if (post.tags.length === 0) {
|
||||||
showEditError('No tags set.');
|
showEditError('No tags set.');
|
||||||
return;
|
return;
|
||||||
|
@ -296,7 +309,7 @@ App.Presenters.PostPresenter = function(
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
post = response.json;
|
post = response.json;
|
||||||
hideEditForm();
|
hideEditForm();
|
||||||
renderSidebar();
|
softRender();
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
showEditError(response);
|
showEditError(response);
|
||||||
});
|
});
|
||||||
|
@ -344,14 +357,14 @@ App.Presenters.PostPresenter = function(
|
||||||
function addFavorite() {
|
function addFavorite() {
|
||||||
promise.wait(api.post('/posts/' + post.id + '/favorites'))
|
promise.wait(api.post('/posts/' + post.id + '/favorites'))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
promise.wait(refreshPost()).then(renderSidebar);
|
promise.wait(refreshPost()).then(softRender);
|
||||||
}).fail(showGenericError);
|
}).fail(showGenericError);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteFavorite() {
|
function deleteFavorite() {
|
||||||
promise.wait(api.delete('/posts/' + post.id + '/favorites'))
|
promise.wait(api.delete('/posts/' + post.id + '/favorites'))
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
promise.wait(refreshPost()).then(renderSidebar);
|
promise.wait(refreshPost()).then(softRender);
|
||||||
}).fail(showGenericError);
|
}).fail(showGenericError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +383,7 @@ App.Presenters.PostPresenter = function(
|
||||||
function score(scoreValue) {
|
function score(scoreValue) {
|
||||||
promise.wait(api.post('/posts/' + post.id + '/score', {score: scoreValue}))
|
promise.wait(api.post('/posts/' + post.id + '/score', {score: scoreValue}))
|
||||||
.then(function() {
|
.then(function() {
|
||||||
promise.wait(refreshPost()).then(renderSidebar);
|
promise.wait(refreshPost()).then(softRender);
|
||||||
}).fail(showGenericError);
|
}).fail(showGenericError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
<div class="post-type-<%= post.contentType %>">
|
<div class="post-type-<%= post.contentType %>">
|
||||||
|
|
||||||
<% if (post.contentType == 'image') { %>
|
<% if (post.contentType === 'image') { %>
|
||||||
|
|
||||||
<img alt="<%= post.name %>" src="<%= postContentUrl %>"/>
|
<img alt="<%= post.name %>" src="<%= postContentUrl %>"/>
|
||||||
|
|
||||||
<% } else if (post.contentType == 'youtube') { %>
|
<% } else if (post.contentType === 'youtube') { %>
|
||||||
|
|
||||||
<iframe src="//www.youtube.com/embed/<%= post.contentChecksum %>?wmode=opaque" allowfullscreen></iframe>
|
<iframe src="//www.youtube.com/embed/<%= post.contentChecksum %>?wmode=opaque" allowfullscreen></iframe>
|
||||||
|
|
||||||
<% } else if (post.contentType == 'flash') { %>
|
<% } else if (post.contentType === 'flash') { %>
|
||||||
|
|
||||||
<object
|
<object
|
||||||
type="<%= post.contentMimeType %>"
|
type="<%= post.contentMimeType %>"
|
||||||
|
@ -21,9 +21,13 @@
|
||||||
<param name="movie" value="<%= postContentUrl %>"/>
|
<param name="movie" value="<%= postContentUrl %>"/>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
<% } else if (post.contentType == 'video') { %>
|
<% } else if (post.contentType === 'video') { %>
|
||||||
|
<% if (post.flags.loop) { %>
|
||||||
|
<video id="video" controls loop="loop">
|
||||||
|
<% } else { %>
|
||||||
|
<video id="video" controls>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<video controls>
|
|
||||||
<source type="<%= post.contentMimeType %>" src="<%= postContentUrl %>"/>
|
<source type="<%= post.contentMimeType %>" src="<%= postContentUrl %>"/>
|
||||||
|
|
||||||
Your browser doesn't support HTML5 videos.
|
Your browser doesn't support HTML5 videos.
|
||||||
|
|
|
@ -48,6 +48,18 @@
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
<% if (privileges.canChangeFlags && post.contentType === 'video') { %>
|
||||||
|
<div class="form-row">
|
||||||
|
<label class="form-label">Loop:</label>
|
||||||
|
<div class="form-input">
|
||||||
|
<input type="checkbox" id="post-loop" name="loop" value="loop" <%= post.flags.loop ? 'checked="checked"' : '' %>/>
|
||||||
|
<label for="post-loop">
|
||||||
|
Automatically repeat video after playback
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<% if (privileges.canChangeContent) { %>
|
<% if (privileges.canChangeContent) { %>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label class="form-label" for="post-content">Content:</label>
|
<label class="form-label" for="post-content">Content:</label>
|
||||||
|
|
|
@ -8,6 +8,7 @@ use Szurubooru\Services\FavoritesService;
|
||||||
use Szurubooru\Services\HistoryService;
|
use Szurubooru\Services\HistoryService;
|
||||||
use Szurubooru\Services\PrivilegeService;
|
use Szurubooru\Services\PrivilegeService;
|
||||||
use Szurubooru\Services\ScoreService;
|
use Szurubooru\Services\ScoreService;
|
||||||
|
use Szurubooru\Entities\Post;
|
||||||
|
|
||||||
class PostViewProxy extends AbstractViewProxy
|
class PostViewProxy extends AbstractViewProxy
|
||||||
{
|
{
|
||||||
|
@ -72,6 +73,8 @@ class PostViewProxy extends AbstractViewProxy
|
||||||
$result->favoriteCount = $post->getFavoriteCount();
|
$result->favoriteCount = $post->getFavoriteCount();
|
||||||
$result->score = $post->getScore();
|
$result->score = $post->getScore();
|
||||||
$result->commentCount = $post->getCommentCount();
|
$result->commentCount = $post->getCommentCount();
|
||||||
|
$result->flags = new \StdClass;
|
||||||
|
$result->flags->loop = ($post->getFlags() & Post::FLAG_LOOP);
|
||||||
|
|
||||||
if (!empty($config[self::FETCH_TAGS]))
|
if (!empty($config[self::FETCH_TAGS]))
|
||||||
$result->tags = $this->tagViewProxy->fromArray($post->getTags());
|
$result->tags = $this->tagViewProxy->fromArray($post->getTags());
|
||||||
|
|
|
@ -25,6 +25,7 @@ class PostEntityConverter extends AbstractEntityConverter implements IEntityConv
|
||||||
'originalFileName' => $entity->getOriginalFileName(),
|
'originalFileName' => $entity->getOriginalFileName(),
|
||||||
'featureCount' => $entity->getFeatureCount(),
|
'featureCount' => $entity->getFeatureCount(),
|
||||||
'lastFeatureTime' => $this->entityTimeToDbTime($entity->getLastFeatureTime()),
|
'lastFeatureTime' => $this->entityTimeToDbTime($entity->getLastFeatureTime()),
|
||||||
|
'flags' => $entity->getFlags(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +47,7 @@ class PostEntityConverter extends AbstractEntityConverter implements IEntityConv
|
||||||
$entity->setOriginalFileName($array['originalFileName']);
|
$entity->setOriginalFileName($array['originalFileName']);
|
||||||
$entity->setFeatureCount(intval($array['featureCount']));
|
$entity->setFeatureCount(intval($array['featureCount']));
|
||||||
$entity->setLastFeatureTime($this->dbTimeToEntityTime($array['lastFeatureTime']));
|
$entity->setLastFeatureTime($this->dbTimeToEntityTime($array['lastFeatureTime']));
|
||||||
|
$entity->setFlags(intval($array['flags']));
|
||||||
$entity->setMeta(Post::META_TAG_COUNT, intval($array['tagCount']));
|
$entity->setMeta(Post::META_TAG_COUNT, intval($array['tagCount']));
|
||||||
$entity->setMeta(Post::META_FAV_COUNT, intval($array['favCount']));
|
$entity->setMeta(Post::META_FAV_COUNT, intval($array['favCount']));
|
||||||
$entity->setMeta(Post::META_COMMENT_COUNT, intval($array['commentCount']));
|
$entity->setMeta(Post::META_COMMENT_COUNT, intval($array['commentCount']));
|
||||||
|
|
|
@ -13,6 +13,8 @@ final class Post extends Entity
|
||||||
const POST_TYPE_VIDEO = 3;
|
const POST_TYPE_VIDEO = 3;
|
||||||
const POST_TYPE_YOUTUBE = 4;
|
const POST_TYPE_YOUTUBE = 4;
|
||||||
|
|
||||||
|
const FLAG_LOOP = 1;
|
||||||
|
|
||||||
const LAZY_LOADER_USER = 'user';
|
const LAZY_LOADER_USER = 'user';
|
||||||
const LAZY_LOADER_TAGS = 'tags';
|
const LAZY_LOADER_TAGS = 'tags';
|
||||||
const LAZY_LOADER_CONTENT = 'content';
|
const LAZY_LOADER_CONTENT = 'content';
|
||||||
|
@ -39,6 +41,7 @@ final class Post extends Entity
|
||||||
private $originalFileName;
|
private $originalFileName;
|
||||||
private $featureCount = 0;
|
private $featureCount = 0;
|
||||||
private $lastFeatureTime;
|
private $lastFeatureTime;
|
||||||
|
private $flags = 0;
|
||||||
|
|
||||||
public function getIdMarkdown()
|
public function getIdMarkdown()
|
||||||
{
|
{
|
||||||
|
@ -195,6 +198,16 @@ final class Post extends Entity
|
||||||
$this->lastFeatureTime = $lastFeatureTime;
|
$this->lastFeatureTime = $lastFeatureTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getFlags()
|
||||||
|
{
|
||||||
|
return $this->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setFlags($flags)
|
||||||
|
{
|
||||||
|
$this->flags = $flags;
|
||||||
|
}
|
||||||
|
|
||||||
public function getTags()
|
public function getTags()
|
||||||
{
|
{
|
||||||
return $this->lazyLoad(self::LAZY_LOADER_TAGS, []);
|
return $this->lazyLoad(self::LAZY_LOADER_TAGS, []);
|
||||||
|
|
|
@ -12,6 +12,7 @@ class PostEditFormData implements IValidatable
|
||||||
public $source;
|
public $source;
|
||||||
public $tags;
|
public $tags;
|
||||||
public $relations;
|
public $relations;
|
||||||
|
public $flags;
|
||||||
|
|
||||||
public $seenEditTime;
|
public $seenEditTime;
|
||||||
|
|
||||||
|
@ -26,6 +27,8 @@ class PostEditFormData implements IValidatable
|
||||||
$this->tags = preg_split('/[\s+]/', $inputReader->tags);
|
$this->tags = preg_split('/[\s+]/', $inputReader->tags);
|
||||||
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
|
$this->relations = array_filter(preg_split('/[\s+]/', $inputReader->relations));
|
||||||
$this->seenEditTime = $inputReader->seenEditTime;
|
$this->seenEditTime = $inputReader->seenEditTime;
|
||||||
|
$this->flags = new \StdClass;
|
||||||
|
$this->flags->loop = !empty($inputReader->flags['loop']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class Privilege
|
||||||
const CHANGE_POST_CONTENT = 'changePostContent';
|
const CHANGE_POST_CONTENT = 'changePostContent';
|
||||||
const CHANGE_POST_THUMBNAIL = 'changePostThumbnail';
|
const CHANGE_POST_THUMBNAIL = 'changePostThumbnail';
|
||||||
const CHANGE_POST_RELATIONS = 'changePostRelations';
|
const CHANGE_POST_RELATIONS = 'changePostRelations';
|
||||||
|
const CHANGE_POST_FLAGS = 'changePostFlags';
|
||||||
|
|
||||||
const LIST_TAGS = 'listTags';
|
const LIST_TAGS = 'listTags';
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,10 @@ class HistoryService
|
||||||
$featuredPostParam = $this->globalParamDao->findByKey(GlobalParam::KEY_FEATURED_POST);
|
$featuredPostParam = $this->globalParamDao->findByKey(GlobalParam::KEY_FEATURED_POST);
|
||||||
$isFeatured = ($featuredPostParam and intval($featuredPostParam->getValue()) === $post->getId());
|
$isFeatured = ($featuredPostParam and intval($featuredPostParam->getValue()) === $post->getId());
|
||||||
|
|
||||||
|
$flags = [];
|
||||||
|
if ($post->getFlags() & Post::FLAG_LOOP)
|
||||||
|
$flags []= 'loop';
|
||||||
|
|
||||||
$data =
|
$data =
|
||||||
[
|
[
|
||||||
'source' => $post->getSource(),
|
'source' => $post->getSource(),
|
||||||
|
@ -128,6 +132,7 @@ class HistoryService
|
||||||
},
|
},
|
||||||
$post->getRelatedPosts()),
|
$post->getRelatedPosts()),
|
||||||
|
|
||||||
|
'flags' => $flags,
|
||||||
];
|
];
|
||||||
|
|
||||||
$snapshot = $this->getPostSnapshot($post);
|
$snapshot = $this->getPostSnapshot($post);
|
||||||
|
|
|
@ -188,6 +188,9 @@ class PostService
|
||||||
if ($formData->relations !== null)
|
if ($formData->relations !== null)
|
||||||
$this->updatePostRelations($post, $formData->relations);
|
$this->updatePostRelations($post, $formData->relations);
|
||||||
|
|
||||||
|
if (count($formData->flags) > 0)
|
||||||
|
$this->updatePostFlags($post, $formData->flags);
|
||||||
|
|
||||||
$this->historyService->saveSnapshot($this->historyService->getPostChangeSnapshot($post));
|
$this->historyService->saveSnapshot($this->historyService->getPostChangeSnapshot($post));
|
||||||
return $this->postDao->save($post);
|
return $this->postDao->save($post);
|
||||||
};
|
};
|
||||||
|
@ -313,6 +316,14 @@ class PostService
|
||||||
$post->setRelatedPosts($relatedPosts);
|
$post->setRelatedPosts($relatedPosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updatePostFlags(Post $post, \StdClass $flags)
|
||||||
|
{
|
||||||
|
$value = 0;
|
||||||
|
if (!empty($flags->loop))
|
||||||
|
$value |= Post::FLAG_LOOP;
|
||||||
|
$post->setFlags($value);
|
||||||
|
}
|
||||||
|
|
||||||
public function deletePost(Post $post)
|
public function deletePost(Post $post)
|
||||||
{
|
{
|
||||||
$transactionFunc = function() use ($post)
|
$transactionFunc = function() use ($post)
|
||||||
|
|
13
src/Upgrades/Upgrade19.php
Normal file
13
src/Upgrades/Upgrade19.php
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
namespace Szurubooru\Upgrades;
|
||||||
|
use Szurubooru\DatabaseConnection;
|
||||||
|
|
||||||
|
class Upgrade19 implements IUpgrade
|
||||||
|
{
|
||||||
|
public function run(DatabaseConnection $databaseConnection)
|
||||||
|
{
|
||||||
|
$pdo = $databaseConnection->getPDO();
|
||||||
|
|
||||||
|
$pdo->exec('ALTER TABLE posts ADD COLUMN flags INT(8) NOT NULL DEFAULT 0');
|
||||||
|
}
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ return [
|
||||||
$container->get(\Szurubooru\Upgrades\Upgrade16::class),
|
$container->get(\Szurubooru\Upgrades\Upgrade16::class),
|
||||||
$container->get(\Szurubooru\Upgrades\Upgrade17::class),
|
$container->get(\Szurubooru\Upgrades\Upgrade17::class),
|
||||||
$container->get(\Szurubooru\Upgrades\Upgrade18::class),
|
$container->get(\Szurubooru\Upgrades\Upgrade18::class),
|
||||||
|
$container->get(\Szurubooru\Upgrades\Upgrade19::class),
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,7 @@ final class HistoryServiceTest extends AbstractTestCase
|
||||||
$post->setContentChecksum('checksum');
|
$post->setContentChecksum('checksum');
|
||||||
$post->setSafety(Post::POST_SAFETY_SKETCHY);
|
$post->setSafety(Post::POST_SAFETY_SKETCHY);
|
||||||
$post->setSource('amazing source');
|
$post->setSource('amazing source');
|
||||||
|
$post->setFlags(Post::FLAG_LOOP);
|
||||||
|
|
||||||
$historyService = $this->getHistoryService();
|
$historyService = $this->getHistoryService();
|
||||||
$snapshot = $historyService->getPostChangeSnapshot($post);
|
$snapshot = $historyService->getPostChangeSnapshot($post);
|
||||||
|
@ -145,7 +146,8 @@ final class HistoryServiceTest extends AbstractTestCase
|
||||||
'contentChecksum' => 'checksum',
|
'contentChecksum' => 'checksum',
|
||||||
'featured' => false,
|
'featured' => false,
|
||||||
'tags' => ['tag1', 'tag2'],
|
'tags' => ['tag1', 'tag2'],
|
||||||
'relations' => [1, 2]
|
'relations' => [1, 2],
|
||||||
|
'flags' => ['loop'],
|
||||||
], $snapshot->getData());
|
], $snapshot->getData());
|
||||||
|
|
||||||
$this->assertEquals(Snapshot::TYPE_POST, $snapshot->getType());
|
$this->assertEquals(Snapshot::TYPE_POST, $snapshot->getType());
|
||||||
|
|
Loading…
Reference in a new issue