search: a bit refactor 2

This commit is contained in:
mrilyew 2024-10-19 20:58:27 +03:00
parent 8286173ed3
commit 4e5bd6433f
19 changed files with 182 additions and 74 deletions

View file

@ -162,6 +162,7 @@ class Playlist extends MediaCollection
"bookmarked" => $this->isBookmarkedBy($user),
"listens" => $this->getListens(),
"cover_url" => $this->getCoverURL(),
"searchable" => !$this->isUnlisted(),
];
}
@ -258,4 +259,9 @@ class Playlist extends MediaCollection
return implode("", $props);
}
function isUnlisted(): bool
{
return (bool)$this->getRecord()->unlisted;
}
}

View file

@ -123,7 +123,7 @@ trait TRichText
$text = preg_replace_callback("%([\n\r\s]|^)(\#([\p{L}_0-9][\p{L}_0-9\(\)\-\']+[\p{L}_0-9\(\)]|[\p{L}_0-9]{1,2}))%Xu", function($m) {
$slug = rawurlencode($m[3]);
return "$m[1]<a href='/feed/hashtag/$slug'>$m[2]</a>";
return "$m[1]<a href='/search?section=posts&q=%23$slug'>$m[2]</a>";
}, $text);
$text = $this->formatEmojis($text);

View file

@ -219,6 +219,7 @@ class Audios
function searchPlaylists(string $query): EntityStream
{
$search = $this->playlists->where([
"unlisted" => 0,
"deleted" => 0,
])->where("MATCH (`name`, `description`) AGAINST (? IN BOOLEAN MODE)", $query);
@ -296,9 +297,28 @@ class Audios
return new Util\EntityStream("Audio", $result);
}
function findPlaylists(string $query, int $page = 1, ?int $perPage = NULL): \Traversable
function findPlaylists(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): \Traversable
{
$result = $this->playlists->where("name LIKE ?", "%$query%");
$result = $this->playlists->where([
"unlisted" => 0,
"deleted" => 0,
])->where("CONCAT_WS(' ', name, description) LIKE ?", "%$query%");
$order_str = 'id';
switch($order['type']) {
case 'id':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
case 'length':
$order_str = 'length ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
case 'listens':
$order_str = 'listens ' . ($order['invert'] ? 'ASC' : 'DESC');
break;
}
if($order_str)
$result->order($order_str);
return new Util\EntityStream("Playlist", $result);
}

View file

@ -75,7 +75,7 @@ final class AudioPresenter extends OpenVKPresenter
if (!$entity || $entity->isBanned())
$this->redirect("/playlists" . $this->user->id);
$playlists = $this->audios->getPlaylistsByClub($entity, $page, 10);
$playlists = $this->audios->getPlaylistsByClub($entity, $page, OPENVK_DEFAULT_PER_PAGE);
$playlistsCount = $this->audios->getClubPlaylistsCount($entity);
} else {
$entity = (new Users)->get($owner);
@ -85,7 +85,7 @@ final class AudioPresenter extends OpenVKPresenter
if(!$entity->getPrivacyPermission("audios.read", $this->user->identity))
$this->flashFail("err", tr("forbidden"), tr("forbidden_comment"));
$playlists = $this->audios->getPlaylistsByUser($entity, $page, 9);
$playlists = $this->audios->getPlaylistsByUser($entity, $page, OPENVK_DEFAULT_PER_PAGE);
$playlistsCount = $this->audios->getUserPlaylistsCount($entity);
}
@ -304,6 +304,8 @@ final class AudioPresenter extends OpenVKPresenter
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$title = $this->postParam("title");
$description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$audios = !empty($this->postParam("audios")) ? array_slice(explode(",", $this->postParam("audios")), 0, 1000) : [];
if(empty($title) || iconv_strlen($title) < 1)
@ -313,7 +315,9 @@ final class AudioPresenter extends OpenVKPresenter
$playlist->setOwner($owner);
$playlist->setName(substr($title, 0, 125));
$playlist->setDescription(substr($description, 0, 2045));
if($is_unlisted == 1)
$playlist->setUnlisted(true);
if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["cover"]["type"], "image"))
$this->flashFail("err", tr("error"), tr("not_a_photo"));
@ -427,6 +431,7 @@ final class AudioPresenter extends OpenVKPresenter
$title = $this->postParam("title");
$description = $this->postParam("description");
$is_unlisted = (int)$this->postParam('is_unlisted');
$new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : [];
if(empty($title) || iconv_strlen($title) < 1)
@ -436,6 +441,7 @@ final class AudioPresenter extends OpenVKPresenter
$playlist->setDescription(ovk_proc_strtr($description, 2045));
$playlist->setEdited(time());
$playlist->resetLength();
$playlist->setUnlisted((bool)$is_unlisted);
if($_FILES["new_cover"]["error"] === UPLOAD_ERR_OK) {
if(!str_starts_with($_FILES["new_cover"]["type"], "image"))
@ -476,12 +482,14 @@ final class AudioPresenter extends OpenVKPresenter
$this->template->playlist = $playlist;
$this->template->page = $page;
$this->template->cover = $playlist->getCoverPhoto();
$this->template->cover_url = $this->template->cover ? $this->template->cover->getURL() : "/assets/packages/static/openvk/img/song.jpg";
$this->template->audios = iterator_to_array($playlist->fetch($page, 10));
$this->template->ownerId = $owner_id;
$this->template->owner = $playlist->getOwner();
$this->template->isBookmarked = $this->user->identity && $playlist->isBookmarkedBy($this->user->identity);
$this->template->isMy = $this->user->identity && $playlist->getOwner()->getId() === $this->user->id;
$this->template->canEdit = $this->user->identity && $playlist->canBeModifiedBy($this->user->identity);
$this->template->count = $playlist->size();
}
function renderAction(int $audio_id): void

View file

@ -48,7 +48,8 @@ final class SearchPresenter extends OpenVKPresenter
"comments" => "comments",
"videos" => "videos",
"audios" => "audios",
"apps" => "apps"
"apps" => "apps",
"audios_playlists" => "audios"
];
$parameters = [
"ignore_private" => true,
@ -92,7 +93,16 @@ final class SearchPresenter extends OpenVKPresenter
$repo = $repos[$section] or $this->throwError(400, "Bad Request", "Invalid search entity $section.");
$results = $this->{$repo}->find($query, $parameters, ['type' => $order, 'invert' => $invert]);
$results = NULL;
switch($section) {
default:
$results = $this->{$repo}->find($query, $parameters, ['type' => $order, 'invert' => $invert]);
break;
case 'audios_playlists':
$results = $this->{$repo}->findPlaylists($query, $parameters, ['type' => $order, 'invert' => $invert]);
break;
}
$iterator = $results->page($page, OPENVK_DEFAULT_PER_PAGE);
$count = $results->size();

View file

@ -127,7 +127,7 @@
<option n:attr="selected => $_REQUEST['section'] == 'videos'" value="videos">{_s_by_videos}</option>
<option n:attr="selected => $_REQUEST['section'] == 'apps'" value="apps">{_s_by_apps}</option>
<option n:attr="selected => $_REQUEST['section'] == 'audios'" value="audios">{_s_by_audios}</option>
<option n:attr="selected => $_REQUEST['section'] == 'playlists'" value="playlists">{_s_by_audios_playlists}</option>
<option n:attr="selected => $_REQUEST['section'] == 'audios_playlists'" value="audios_playlists">{_s_by_audios_playlists}</option>
</select>
</div>
<button class="search_box_button">

View file

@ -5,7 +5,7 @@
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»
<a href="/audios{$ownerId}">{_audios}</a>
<a href="/playlists{$ownerId}">{_playlists}</a>
»
<a href="/playlist{$playlist->getPrettyId()}">{_playlist}</a>
»
@ -30,8 +30,13 @@
</div>
<div class="moreInfo">
<textarea name="description" maxlength="2045" style="margin-top: 11px;">{$playlist->getDescription()}</textarea>
<textarea placeholder="{_description}" name="description" maxlength="2045" style="margin-top: 11px;">{$playlist->getDescription()}</textarea>
</div>
<label>
<input type='checkbox' name='is_unlisted' n:attr='checked => $playlist->isUnlisted()'>
{_playlist_hide_from_search}
</label>
</div>
</div>
@ -56,6 +61,7 @@
<form method="post" id="editPlaylistForm" data-id="{$playlist->getId()}" enctype="multipart/form-data">
<input type="hidden" name="title" maxlength="128" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="hidden" name="is_unlisted" value="0" />
<textarea style="display:none;" name="description" maxlength="2048" />
<input type="hidden" name="audios">
<input type="file" style="display:none;" name="new_cover" accept=".jpg,.png">
@ -71,6 +77,7 @@
u("#editPlaylistForm").on("submit", (e) => {
document.querySelector("#editPlaylistForm input[name='title']").value = document.querySelector(".playlistInfo input[name='title']").value
document.querySelector("#editPlaylistForm textarea[name='description']").value = document.querySelector(".playlistBlock textarea[name='description']").value
document.querySelector("#editPlaylistForm input[name='is_unlisted']").value = document.querySelector("input[name='is_unlisted']").checked ? 1 : 0
})
u("#editPlaylistForm input[name='new_cover']").on("change", (e) => {

View file

@ -81,30 +81,15 @@
</div>
</div>
<div style="width: 74%;" class="audiosPaddingContainer" n:if="$mode == 'playlists'">
<div style="width: 71.8%;" class="audiosPaddingContainer audiosPaddingContainer" n:if="$mode == 'playlists'">
<div n:if="$playlistsCount <= 0" style='height: 100%;'>
{include "../components/content_error.xml", description => $ownerId > 0 ? ($ownerId == $thisUser->getId() ? tr("no_playlists_thisuser") : tr("no_playlists_user")) : tr("no_playlists_club")}
</div>
<div class="infContainer playlistContainer" n:if="$playlistsCount > 0">
<div n:foreach="$playlists as $playlist">
<a href="/playlist{$playlist->getPrettyId()}">
<div class="playlistCover">
<img src="{$playlist->getCoverURL()}" alt="{_playlist_cover}">
</div>
</a>
<div class="playlistInfo">
<a href="/playlist{$playlist->getPrettyId()}">
<span style="font-size: 12px" class="playlistName">
{ovk_proc_strtr($playlist->getName(), 15)}
</span>
</a>
<a href="{$playlist->getOwner()->getURL()}">{ovk_proc_strtr($playlist->getOwner()->getCanonicalName(), 20)}</a>
</div>
</div>
{foreach $playlists as $playlist}
{include 'playlistListView.xml', playlist => $playlist}
{/foreach}
</div>
<div>

View file

@ -8,11 +8,11 @@
{if !$_GET["gid"]}
<a href="{$thisUser->getURL()}">{$thisUser->getCanonicalName()}</a>
»
<a href="/audios{$thisUser->getId()}">{_audios}</a>
<a href="/playlists{$thisUser->getId()}">{_playlists}</a>
{else}
<a href="{$club->getURL()}">{$club->getCanonicalName()}</a>
»
<a href="/audios-{$club->getId()}">{_audios}</a>
<a href="/playlists-{$club->getId()}">{_playlists}</a>
{/if}
»
{_new_playlist}
@ -44,6 +44,10 @@
<div class="moreInfo" style="margin-top: 11px;">
<textarea placeholder="{_description}" name="description" maxlength="2045" />
</div>
<label>
<input type='checkbox' name='is_unlisted'>
{_playlist_hide_from_search}
</label>
</div>
</div>
@ -68,6 +72,7 @@
<form method="post" id="newPlaylistForm" enctype="multipart/form-data">
<input type="hidden" name="title" maxlength="125" />
<input type="hidden" name="hash" value="{$csrfToken}" />
<input type="hidden" name="is_unlisted" value="0" />
<textarea style="display:none;" name="description" maxlength="2045" />
<input type="hidden" name="audios">
<input type="file" style="display:none;" name="cover" accept=".jpg,.png">
@ -83,6 +88,7 @@
u("#newPlaylistForm").on("submit", (e) => {
document.querySelector("#newPlaylistForm input[name='title']").value = document.querySelector(".plinfo input[name='title']").value
document.querySelector("#newPlaylistForm textarea[name='description']").value = document.querySelector(".plinfo textarea[name='description']").value
document.querySelector("#newPlaylistForm input[name='is_unlisted']").value = document.querySelector(".plinfo input[name='is_unlisted']").checked ? 1 : 0
})
u("#newPlaylistForm input[name='cover']").on("change", (e) => {

View file

@ -1,13 +1,16 @@
{extends "../@layout.xml"}
{block title}{_playlist}{/block}
{block title}
{_playlist}
{$playlist->getName()}
{/block}
{block headIncludes}
<meta property="og:type" content="music.album">
<meta property="og:title" content="{$playlist->getName()}">
<meta property="og:url" content="{$playlist->getURL()}">
<meta property="og:description" content="{$playlist->getDescription()}">
<meta property="og:image" content="{$playlist->getCoverURL()}">
<meta property="og:image" content="{$cover_url}">
<script type="application/ld+json">
{
@ -22,31 +25,35 @@
{block header}
<a href="{$owner->getURL()}">{$owner->getCanonicalName()}</a>
»
<a href="/audios{$ownerId}">{_audios}</a>
<a href="/playlists{$ownerId}">{_playlists}</a>
»
{_playlist}
{/block}
{block content}
{include "bigplayer.xml"}
{php $count = $playlist->size()}
<input type="hidden" name="bigplayer_context" data-type="playlist_context" data-entity="{$playlist->getId()}" data-page="{$page}">
<div class="playlistBlock">
<div class="playlistCover" style="float: left;">
<a href="{$cover->getURL()}" target="_blank">
<img onclick="OpenMiniature(event, {$cover->getURL()}, null, {$cover->getPrettyId()}, null)" src="{$playlist->getCoverURL('normal')}" alt="{_playlist_cover}">
{if $cover}
<a href="{$cover_url}" target="_blank">
<img onclick="OpenMiniature(event, {$cover_url}, null, {$cover->getPrettyId()}, null)" src="{$cover_url}" alt="{_playlist_cover}">
</a>
{else}
<a>
<img src="{$cover_url}" alt="{_playlist_cover}">
</a>
{/if}
<div class="profile_links" style="width: 139px;" n:if="isset($thisUser)">
<a class="profile_link" style="width: 98%;" href="/playlist{$playlist->getPrettyId()}/edit" n:if="$playlist->canBeModifiedBy($thisUser)">{_edit_playlist}</a>
<a class="profile_link" style="width: 98%;" id="bookmarkPlaylist" data-id="{$playlist->getId()}" n:if="!$isBookmarked">{_bookmark}</a>
<a class="profile_link" style="width: 98%;" id="unbookmarkPlaylist" data-id="{$playlist->getId()}" n:if="$isBookmarked">{_unbookmark}</a>
<div class="profile_links" n:if="isset($thisUser)">
<a class="profile_link" href="/playlist{$playlist->getPrettyId()}/edit" n:if="$playlist->canBeModifiedBy($thisUser)">{_edit_playlist}</a>
<a class="profile_link" id="bookmarkPlaylist" data-id="{$playlist->getId()}" n:if="!$isBookmarked">{_bookmark}</a>
<a class="profile_link" id="unbookmarkPlaylist" data-id="{$playlist->getId()}" n:if="$isBookmarked">{_unbookmark}</a>
</div>
</div>
<div style="float: left;padding-left: 13px;width:75%">
<div class='playlistWrapper'>
<div class="playlistInfo">
<h4 style="border-bottom:unset;">{$playlist->getName()}</h4>

View file

@ -0,0 +1,20 @@
<a href="/playlist{$playlist->getPrettyId()}" class='playlistListView'>
<div class="playlistCover">
<img src="{$playlist->getCoverURL('normal')}" alt="{_playlist_cover}">
</div>
<div class="playlistInfo">
<div class="playlistInfoTopPart">
<span class="playlistName noOverflow">
{$playlist->getName(), 15}
</span>
<span n:if='!empty($playlist->getDescription())' class="playlistDesc noOverflow">
{$playlist->getDescription()}
</span>
</div>
<span class="playlistMeta">
{$playlist->getMetaDescription()|noescape}
</span>
</div>
</a>

View file

@ -31,7 +31,7 @@
<div class="additionalInfo">
{php $audioStatus = $fr->getCurrentAudioStatus()}
<span class="name">{$fr->getCanonicalName()}</span>
<span class="name noOverflow">{$fr->getCanonicalName()}</span>
<span class="desc">{$audioStatus ? $audioStatus->getName() : tr("audios_count", $fr->getAudiosCollectionSize())}</span>
</div>
</div>

View file

@ -220,6 +220,10 @@
<div class='search_content' n:foreach="$data as $dat">
{include "../Audio/player.xml", audio => $dat}
</div>
{elseif $section === 'audios_playlists'}
<div class='search_content' n:foreach="$data as $dat">
{include "../Audio/playlistListView.xml", playlist => $dat}
</div>
{/if}
{else}
{include "../components/content_error.xml", description => tr("no_results_by_this_query")}
@ -234,7 +238,7 @@
<a n:attr="id => $section === 'videos' ? 'used'" href="/search?section=videos&q={$query}"> {_s_videos}</a>
<a n:attr="id => $section === 'apps' ? 'used'" href="/search?section=apps&q={$query}"> {_s_apps}</a>
<a n:attr="id => $section === 'audios' ? 'used'" href="/search?section=audios&q={$query}"> {_s_audios}</a>
<a n:attr="id => $section === 'playlists' ? 'used'" href="/search?section=playlists&q={$query}">{_s_audios_playlists}</a>
<a n:attr="id => $section === 'audios_playlists' ? 'used'" href="/search?section=audios_playlists&q={$query}">{_s_audios_playlists}</a>
</div>
<div class='page_search_options'>
@ -273,7 +277,7 @@
<option value="id" n:attr="selected => $order == 'id'">{_s_order_by_creation_date}</option>
{/if}
{if $section == "audios"}
{if $section == "audios" || $section == "audios_playlists"}
<option value="length" n:attr="selected => $order == 'length'">{_s_order_by_length}</option>
<option value="listens" n:attr="selected => $order == 'listens'">{_s_order_by_listens}</option>
{/if}

View file

@ -433,25 +433,55 @@
margin-top: 14px;
}
.playlistContainer {
display: grid;
grid-template-columns: repeat(3, 146px);
gap: 18px 10px;
.playlistBlock .playlistWrapper {
float: left;
padding-left: 13px;
width:75%
}
.playlistContainer .playlistCover {
width: 111px;
height: 111px;
.playlistCover .profile_links .profile_link {
width: 100%;
}
/* playlist listview */
.playlistListView {
display: flex;
background: #c4c4c4;
padding: 7px;
gap: 9px;
cursor: pointer;
}
.playlistContainer .playlistCover img {
max-width: 111px;
max-height: 111px;
margin: auto;
.playlistListView:hover, .playlistListView .playlistCover {
background: #ebebeb;
}
.playlistListView .playlistCover img {
width: 100px;
height: 100px;
object-fit: contain;
}
.playlistListView .playlistInfo, .playlistListView .playlistInfo .playlistInfoTopPart {
display: flex;
flex-direction: column;
}
.playlistListView .playlistInfo {
gap: 2px;
overflow: hidden;
}
.playlistListView .playlistInfo .playlistName {
font-weight: 600;
}
.playlistListView .playlistInfo .playlistMeta, .playlistListView .playlistInfo .playlistMeta span {
color: #676767;
}
/* other */
.ovk-diag-body .searchBox {
background: #e6e6e6;
padding-top: 10px;
@ -496,10 +526,6 @@
cursor: pointer;
}
.playlistCover img {
cursor: pointer;
}
.explicitMark {
margin-top: 2px;
margin-left: 3px;
@ -540,15 +566,6 @@
margin-left: -10px;
}
.playlistInfo {
display: flex;
flex-direction: column;
}
.playlistInfo .playlistName {
font-weight: 600;
}
.searchList.floating {
position: fixed;
z-index: 199;

View file

@ -2625,6 +2625,7 @@ a.poll-retract-vote {
right: 0;
text-align: right;
border-left: 0px;
background: transparent;
}
.header_navigation #search_box .search_box_button {

View file

@ -746,7 +746,20 @@ function initPlayer(id, keys, url, length) {
playButton.removeClass("paused")
document.querySelector('link[rel="icon"], link[rel="shortcut icon"]').setAttribute("href", "/assets/packages/static/openvk/img/favicons/favicon24_playing.png")
}
u('.subTracks .lengthTrack').nodes.forEach(el => {
if(el && (el.style.display == 'block' || el.style.display == '')) {
const audioEmbed = el.closest('.audioEmbed')
if(audioEmbed.dataset && Number(audioEmbed.dataset.realid) == Number(playerObject.dataset.realid)) {
return
}
el.style.display = 'none'
audioEmbed.querySelector('.volumeTrack').style.display = 'none'
}
})
if(!$(`#audioEmbed-${ id}`).hasClass("havePlayed")) {
$(`#audioEmbed-${ id}`).addClass("havePlayed")
@ -766,7 +779,7 @@ function initPlayer(id, keys, url, length) {
u(audio).on("play", playButtonImageUpdate);
u(audio).on(["pause", "suspended"], playButtonImageUpdate);
u(audio).on("ended", (e) => {
let thisPlayer = e.target.closest(".audioEmbed")
let thisPlayer = playerObject
let nextPlayer = null
if(thisPlayer.closest(".attachment") != null) {
try {

View file

@ -0,0 +1 @@
ALTER TABLE `playlists` ADD `unlisted` TINYINT(1) UNSIGNED DEFAULT '0' AFTER `deleted`;

View file

@ -646,6 +646,7 @@
"search_for_apps" = "Search for apps";
"search_for_notes" = "Search for notes";
"search_for_audios" = "Search for music";
"search_for_audios_playlists" = "Search for playlists";
"search_button" = "Find";
"search_placeholder" = "Start typing any name, title or word";
"results_zero" = "No results";

View file

@ -621,6 +621,7 @@
"search_for_apps" = "Поиск приложений";
"search_for_notes" = "Поиск записок";
"search_for_audios" = "Поиск музыки";
"search_for_audios_playlists" = "Поиск плейлистов";
"search_button" = "Найти";
"search_placeholder" = "Начните вводить любое имя, название или слово";
"results_zero" = "Ни одного результата";
@ -904,6 +905,7 @@
"repeat_tip" = "Повторение";
"shuffle_tip" = "Перемешать";
"mute_tip" = "Заглушить";
"playlist_hide_from_search" = "Не показывать в поиске";
/* Notifications */