From d165307993265a9fcca9c148da57c1ec900e77d2 Mon Sep 17 00:00:00 2001
From: mrilyew <99399973+mrilyew@users.noreply.github.com>
Date: Mon, 21 Oct 2024 22:01:11 +0300
Subject: [PATCH] upload page changes, add playlist add menu
---
VKAPI/Handlers/Audio.php | 26 ++-
Web/Models/Repositories/Audios.php | 15 +-
Web/Models/Repositories/Posts.php | 1 +
Web/Presenters/AudioPresenter.php | 69 ++++++-
Web/Presenters/SearchPresenter.php | 1 +
Web/Presenters/templates/Audio/Upload.xml | 41 +++-
Web/Presenters/templates/Audio/player.xml | 5 +-
Web/Presenters/templates/Search/Index.xml | 1 -
Web/static/css/audios.css | 24 +++
Web/static/css/main.css | 57 +++++-
Web/static/js/al_music.js | 236 ++++++++++++++++++----
locales/en.strings | 17 +-
locales/ru.strings | 10 +
13 files changed, 431 insertions(+), 72 deletions(-)
diff --git a/VKAPI/Handlers/Audio.php b/VKAPI/Handlers/Audio.php
index 004daef4..90d060fb 100644
--- a/VKAPI/Handlers/Audio.php
+++ b/VKAPI/Handlers/Audio.php
@@ -581,13 +581,31 @@ final class Audio extends VKAPIRequestHandler
];
}
- function searchAlbums(string $query, int $offset = 0, int $limit = 25, int $drop_private = 0): object
+ function searchAlbums(string $query = '', int $offset = 0, int $limit = 25, int $drop_private = 0, int $order = 0, int $from_me = 0): object
{
$this->requireUser();
$playlists = [];
- $search = (new Audios)->searchPlaylists($query)->offsetLimit($offset, $limit);
- foreach($search as $playlist) {
+ $params = [];
+ $order_str = 'id';
+ switch($order) {
+ default:
+ case 0:
+ $order_str = 'id';
+ break;
+ case 1:
+ $order_str = 'length';
+ break;
+ case 2:
+ $order_str = 'listens';
+ break;
+ }
+
+ if($from_me === 1)
+ $params['from_me'] = $this->getUser()->getId();
+
+ $search = (new Audios)->findPlaylists($query, $params, ['type' => $order_str, 'invert' => false]);
+ foreach($search->offsetLimit($offset, $limit) as $playlist) {
if(!$playlist->canBeViewedBy($this->getUser())) {
if($drop_private == 0)
$playlists[] = NULL;
@@ -599,7 +617,7 @@ final class Audio extends VKAPIRequestHandler
}
return (object) [
- "count" => sizeof($playlists),
+ "count" => $search->size(),
"items" => $playlists,
];
}
diff --git a/Web/Models/Repositories/Audios.php b/Web/Models/Repositories/Audios.php
index 22e7ac1e..1ec45dd2 100644
--- a/Web/Models/Repositories/Audios.php
+++ b/Web/Models/Repositories/Audios.php
@@ -300,11 +300,13 @@ class Audios
function findPlaylists(string $query, array $params = [], array $order = ['type' => 'id', 'invert' => false]): \Traversable
{
$result = $this->playlists->where([
- "unlisted" => 0,
"deleted" => 0,
])->where("CONCAT_WS(' ', name, description) LIKE ?", "%$query%");
$order_str = 'id';
+ if(is_null($params['from_me']) || empty($params['from_me']))
+ $result->where(["unlisted" => 0]);
+
switch($order['type']) {
case 'id':
$order_str = 'id ' . ($order['invert'] ? 'ASC' : 'DESC');
@@ -317,6 +319,17 @@ class Audios
break;
}
+ foreach($params as $paramName => $paramValue) {
+ if(is_null($paramValue) || $paramValue == '') continue;
+
+ switch($paramName) {
+ # БУДЬ МАКСИМАЛЬНО АККУРАТЕН С ДАННЫМ ПАРАМЕТРОМ
+ case "from_me":
+ $result->where("owner", $paramValue);
+ break;
+ }
+ }
+
if($order_str)
$result->order($order_str);
diff --git a/Web/Models/Repositories/Posts.php b/Web/Models/Repositories/Posts.php
index 31406771..0c9a78f9 100644
--- a/Web/Models/Repositories/Posts.php
+++ b/Web/Models/Repositories/Posts.php
@@ -181,6 +181,7 @@ class Posts
case 'ads':
$result->where("ad", 1);
break;*/
+ # БУДЬ МАКСИМАЛЬНО АККУРАТЕН С ДАННЫМ ПАРАМЕТРОМ
case 'from_me':
$result->where("owner", $paramValue);
break;
diff --git a/Web/Presenters/AudioPresenter.php b/Web/Presenters/AudioPresenter.php
index e1584366..b47ba0f0 100644
--- a/Web/Presenters/AudioPresenter.php
+++ b/Web/Presenters/AudioPresenter.php
@@ -567,16 +567,65 @@ final class AudioPresenter extends OpenVKPresenter
break;
case "add_to_club":
- $club = (new Clubs)->get((int)$this->postParam("club"));
-
- if(!$club || !$club->canBeModifiedBy($this->user->identity))
- $this->flashFail("err", "error", tr("access_denied"), null, true);
-
- if(!$audio->isInLibraryOf($club))
- $audio->add($club);
- else
- $this->flashFail("err", "error", tr("group_has_audio"), null, true);
-
+ $detailed = [];
+ if($audio->isWithdrawn())
+ $this->flashFail("err", "error", tr("invalid_audio"), null, true);
+
+ if(empty($this->postParam("clubs")))
+ $this->flashFail("err", "error", 'clubs not passed', null, true);
+
+ $clubs_arr = explode(',', $this->postParam("clubs"));
+ $count = sizeof($clubs_arr);
+ if($count < 1 || $count > 10) {
+ $this->flashFail("err", "error", tr('too_many_or_to_lack'), null, true);
+ }
+
+ foreach($clubs_arr as $club_id) {
+ $club = (new Clubs)->get((int)$club_id);
+ if(!$club || !$club->canBeModifiedBy($this->user->identity))
+ continue;
+
+ if(!$audio->isInLibraryOf($club)) {
+ $detailed[$club_id] = true;
+ $audio->add($club);
+ } else {
+ $detailed[$club_id] = false;
+ continue;
+ }
+ }
+
+ $this->returnJson(["success" => true, 'detailed' => $detailed]);
+ break;
+ case "add_to_playlist":
+ $detailed = [];
+ if($audio->isWithdrawn())
+ $this->flashFail("err", "error", tr("invalid_audio"), null, true);
+
+ if(empty($this->postParam("playlists")))
+ $this->flashFail("err", "error", 'playlists not passed', null, true);
+
+ $playlists_arr = explode(',', $this->postParam("playlists"));
+ $count = sizeof($playlists_arr);
+ if($count < 1 || $count > 10) {
+ $this->flashFail("err", "error", tr('too_many_or_to_lack'), null, true);
+ }
+
+ foreach($playlists_arr as $playlist_id) {
+ $pid = explode('_', $playlist_id);
+ $playlist = (new Audios)->getPlaylistByOwnerAndVID((int)$pid[0], (int)$pid[1]);
+ if(!$playlist || !$playlist->canBeModifiedBy($this->user->identity))
+ continue;
+
+ if(!$playlist->hasAudio($audio)) {
+ $playlist->add($audio);
+ $detailed[$playlist_id] = true;
+ } else {
+ $detailed[$playlist_id] = false;
+ continue;
+ }
+ }
+
+ $this->returnJson(["success" => true, 'detailed' => $detailed]);
break;
case "delete":
if($audio->canBeModifiedBy($this->user->identity))
diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php
index bc4b8d8d..47f3ee16 100644
--- a/Web/Presenters/SearchPresenter.php
+++ b/Web/Presenters/SearchPresenter.php
@@ -83,6 +83,7 @@ final class SearchPresenter extends OpenVKPresenter
$parameters['with_lyrics'] = true;
break;
+ # дай бог работал этот case
case 'from_me':
if((int) $param_value != 1) continue;
$parameters['from_me'] = $this->user->id;
diff --git a/Web/Presenters/templates/Audio/Upload.xml b/Web/Presenters/templates/Audio/Upload.xml
index da2cc0fc..96f402f7 100644
--- a/Web/Presenters/templates/Audio/Upload.xml
+++ b/Web/Presenters/templates/Audio/Upload.xml
@@ -110,8 +110,24 @@
document.querySelector("#firstStep").style.display = "none"
document.querySelector("#lastStep").style.display = "block"
+
+ function fallback() {
+ console.info('Tags not found, setting default values.')
+
+ document.querySelector("#lastStep input[name=name]").value = files[0].name
+ document.querySelector("#lastStep select[name=genre]").value = "Other"
+ document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
+ }
- const tags = await id3.fromFile(files[0]);
+ let tags = null
+
+ try {
+ tags = await id3.fromFile(files[0]);
+ } catch(e) {
+ console.error(e)
+ }
+
+ console.log(tags)
if(tags != null) {
console.log("ID" + tags.kind + " detected, setting values...");
@@ -126,11 +142,22 @@
document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
if(tags.genre != null) {
- if(document.querySelector("#lastStep select[name=genre] > option[value='" + tags.genre + "']") != null) {
- document.querySelector("#lastStep select[name=genre]").value = tags.genre;
+ // if there are more than one genre
+ if(tags.genre.split(', ').length > 1) {
+ const genres = tags.genre.split(', ')
+
+ genres.forEach(genre => {
+ if(document.querySelector("#lastStep select[name=genre] > option[value='" + genre + "']") != null) {
+ document.querySelector("#lastStep select[name=genre]").value = genre;
+ }
+ })
} else {
- console.warn("Unknown genre: " + tags.genre);
- document.querySelector("#lastStep select[name=genre]").value = "Other"
+ if(document.querySelector("#lastStep select[name=genre] > option[value='" + tags.genre + "']") != null) {
+ document.querySelector("#lastStep select[name=genre]").value = tags.genre;
+ } else {
+ console.warn("Unknown genre: " + tags.genre);
+ document.querySelector("#lastStep select[name=genre]").value = "Other"
+ }
}
} else {
document.querySelector("#lastStep select[name=genre]").value = "Other"
@@ -139,9 +166,7 @@
if(tags.comments != null)
document.querySelector("#lastStep textarea[name=lyrics]").value = tags.comments
} else {
- document.querySelector("#lastStep input[name=name]").value = files[0].name
- document.querySelector("#lastStep select[name=genre]").value = "Other"
- document.querySelector("#lastStep input[name=performer]").value = tr("track_unknown");
+ fallback()
}
});
diff --git a/Web/Presenters/templates/Audio/player.xml b/Web/Presenters/templates/Audio/player.xml
index f235dc93..de8f03d7 100644
--- a/Web/Presenters/templates/Audio/player.xml
+++ b/Web/Presenters/templates/Audio/player.xml
@@ -1,7 +1,8 @@
{php $id = $audio->getId() . rand(0, 1000)}
{php $isWithdrawn = $audio->isWithdrawn()}
+{php $isAvailable = $audio->isAvailable()}
{php $editable = isset($thisUser) && $audio->canBeModifiedBy($thisUser)}
-
getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$audio->isAvailable()}processed{/if} {if $isWithdrawn}withdrawn{/if}" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
+
getPrettyId()}" data-name="{$audio->getName()}"{/if} data-genre="{$audio->getGenre()}" class="audioEmbed {if !$isAvailable}processed{/if} {if $isWithdrawn}withdrawn{/if}" data-length="{$audio->getLength()}" data-keys="{json_encode($audio->getKeys())}" data-url="{$audio->getURL()}">
@@ -36,7 +37,7 @@
-
+
{/if}
diff --git a/Web/Presenters/templates/Search/Index.xml b/Web/Presenters/templates/Search/Index.xml
index 2cbf94c5..77c2bd43 100644
--- a/Web/Presenters/templates/Search/Index.xml
+++ b/Web/Presenters/templates/Search/Index.xml
@@ -213,7 +213,6 @@
highlightText({$query}, '.page_wrap_content_main', ['text', "span[data-highlight='_appDesc']"])
{elseif $section === 'posts'}
- {* шмалим дурь курим шмаль дуем коку пиво пьём *}
{if !$dat || $dat->getWallOwner()->isHideFromGlobalFeedEnabled()}
{_closed_group_post}.
diff --git a/Web/static/css/audios.css b/Web/static/css/audios.css
index 6a602e51..4ac39091 100644
--- a/Web/static/css/audios.css
+++ b/Web/static/css/audios.css
@@ -764,3 +764,27 @@
.addToPlaylist {
width: 22%;
}
+
+#_addAudioAdditional {
+ padding: 6px;
+}
+
+#_addAudioAdditional #_tabs {
+ margin: 0px -6px;
+}
+
+#_addAudioAdditional #_tabs .mb_tabs {
+ background-color: unset;
+ border-bottom: unset;
+ padding: 0px 5px;
+}
+
+#_addAudioAdditional #_tip {
+ margin: 5px 0px;
+ display: block;
+}
+
+#_addAudioAdditional #_content {
+ margin-top: 6px;
+ padding: 1px;
+}
diff --git a/Web/static/css/main.css b/Web/static/css/main.css
index cf078a33..f60fffbd 100644
--- a/Web/static/css/main.css
+++ b/Web/static/css/main.css
@@ -2102,12 +2102,13 @@ table td[width="120"] {
border-radius: 2px;
}
-.mb_tab div {
+.mb_tab div, .mb_tab > a {
padding: 5px 9px;
+ display: block;
}
.mb_tab#active {
- background-color: #898989;
+ background-color: #606060;
}
.mb_tab#active a {
@@ -2584,7 +2585,7 @@ a.poll-retract-vote {
.page_header.search_expanded .header_navigation #search_box #searchBoxFastTips.shown {
display: flex;
flex-direction: column;
- z-index: 1;
+ z-index: 2;
}
.page_header.search_expanded .link {
@@ -3211,3 +3212,53 @@ hr {
background-position: 50% !important;
}
+.entity_vertical_list {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ height: 197px;
+ overflow-y: auto;
+}
+
+.entity_vertical_list .entity_vertical_list_item {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ gap: 4px;
+}
+
+.entity_vertical_list .entity_vertical_list_item .first_column {
+ display: flex;
+ flex-direction: row;
+ gap: 4px;
+}
+
+.entity_vertical_list .entity_vertical_list_item .avatar {
+ display: block;
+}
+
+.entity_vertical_list .entity_vertical_list_item img {
+ width: 50px;
+ height: 50px;
+ object-fit: cover;
+}
+
+.entity_vertical_list .entity_vertical_list_item .info {
+ width: 300px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.entity_vertical_list.mini .entity_vertical_list_item img {
+ width: 35px;
+ height: 35px;
+}
+
+#gif_loader {
+ content: "";
+ display: inline-block;
+ background-image: url('/assets/packages/static/openvk/img/loading_mini.gif');
+ width: 30px;
+ height: 7px;
+}
diff --git a/Web/static/js/al_music.js b/Web/static/js/al_music.js
index c495dffb..c792926e 100644
--- a/Web/static/js/al_music.js
+++ b/Web/static/js/al_music.js
@@ -993,7 +993,7 @@ $(document).on("click", ".musicIcon.remove-icon", (e) => {
e.stopImmediatePropagation()
const id = e.currentTarget.dataset.id
- if(e.altKey) {
+ if(e.detail > 1 || e.altKey) {
const player = e.target.closest('.audioEmbed')
player.querySelector('.add-icon-group').click()
return
@@ -1061,60 +1061,214 @@ $(document).on("click", ".musicIcon.remove-icon-group", (e) => {
})
$(document).on("click", ".musicIcon.add-icon-group", async (ev) => {
- let body = `
- ${tr("what_club_add")}
-
-
-
+ let current_tab = 'club';
+ const id = Number(ev.target.dataset.id)
+ const body = `
+
+
+
${tr('add_audio_limitations')}
+
-
`
- MessageBox(tr("add_audio_to_club"), body, [tr("close")], [Function.noop])
- document.querySelector(".ovk-diag-body").style.padding = "11px"
-
- if(window.openvk.writeableClubs == null) {
- try {
- window.openvk.writeableClubs = await API.Groups.getWriteableClubs()
- } catch (e) {
- document.querySelector(".errorPlace").innerHTML = tr("no_access_clubs")
- document.querySelector(".ovk-diag-body input[name='addButton']").classList.add("lagged")
+ MessageBox(tr("add_audio"), body, [tr("cancel"), tr("add")], [Function.noop, () => {
+ const ids = []
+ u('#_content .entity_vertical_list_item').nodes.forEach(item => {
+ const _checkbox = item.querySelector(`input[type='checkbox'][name='add_to']`)
+ if(_checkbox.checked) {
+ ids.push(item.dataset.id)
+ }
+ })
+ if(ids.length < 1 || ids.length > 10) {
return
}
+
+ console.log(ids)
+
+ switch(current_tab) {
+ case 'club':
+ $.ajax({
+ type: "POST",
+ url: `/audio${id}/action?act=add_to_club`,
+ data: {
+ hash: u("meta[name=csrf]").attr("value"),
+ clubs: ids.join(',')
+ },
+ success: (response) => {
+ if(!response.success)
+ fastError(response.flash.message)
+ else
+ NewNotification(tr("audio_was_successfully_added"), '')
+ }
+ })
+
+ break
+ case 'playlist':
+ $.ajax({
+ type: "POST",
+ url: `/audio${id}/action?act=add_to_playlist`,
+ data: {
+ hash: u("meta[name=csrf]").attr("value"),
+ playlists: ids.join(',')
+ },
+ success: (response) => {
+ if(!response.success)
+ fastError(response.flash.message)
+ else
+ NewNotification(tr("audio_was_successfully_added"), '')
+ }
+ })
+
+ break
+ }
+ }])
+
+ u(".ovk-diag-body").attr('style', 'padding:0px;height: 260px;')
+
+ async function switchTab(tab = 'club') {
+ current_tab = tab
+ u(`#_addAudioAdditional .mb_tab`).attr('id', 'ki')
+ u(`#_addAudioAdditional .mb_tab[data-name='${tab}']`).attr('id', 'active')
+
+ switch(tab) {
+ case 'club':
+ u("#_content").html(`
`)
+ if(window.openvk.writeableClubs == null) {
+ u('.entity_vertical_list').append(`
`)
+
+ try {
+ window.openvk.writeableClubs = await API.Groups.getWriteableClubs()
+ } catch (e) {
+ u("#_content").html(tr("no_access_clubs"))
+
+ return
+ }
+
+ u('.entity_vertical_list #gif_loader').remove()
+ }
+
+ window.openvk.writeableClubs.forEach(el => {
+ u("#_content .entity_vertical_list").append(`
+
+ `)
+ })
+ break
+ case 'playlist':
+ const per_page = 10
+ let page = 0
+ u("#_content").html(`
`)
+
+ async function recievePlaylists(s_page) {
+ res = await fetch(`/method/audio.searchAlbums?auth_mechanism=roaming&query=&limit=10&offset=${s_page * per_page}&from_me=1`)
+ res = await res.json()
+
+ return res
+ }
+
+ function appendPlaylists(response) {
+ response.items.forEach(el => {
+ u("#_content .entity_vertical_list").append(`
+
+ `)
+ })
+
+ if(response.count > per_page * page) {
+ u("#_content .entity_vertical_list").append(`
${tr('show_more')}`)
+ }
+ }
+
+ if(window.openvk.writeablePlaylists == null) {
+ u('.entity_vertical_list').append(`
`)
+
+ try {
+ res = await recievePlaylists(page)
+ page += 1
+ window.openvk.writeablePlaylists = res.response
+
+ if(!window.openvk.writeablePlaylists || window.openvk.writeablePlaylists.count < 1) {
+ throw new Error
+ }
+ } catch (e) {
+ u("#_content").html(tr("no_access_playlists"))
+
+ return
+ }
+
+ u('.entity_vertical_list #gif_loader').remove()
+ }
+
+ appendPlaylists(window.openvk.writeablePlaylists)
+
+ u('#_addAudioAdditional').on('click', '#_pladdwinshowmore', async (e) => {
+ e.target.outerHTML = ''
+
+ res = await recievePlaylists(page)
+ page += 1
+
+ appendPlaylists(res.response)
+ })
+ break
+ }
}
- window.openvk.writeableClubs.forEach(el => {
- document.querySelector("#addIconsWindow").insertAdjacentHTML("beforeend", `
-
- `)
+ switchTab(current_tab)
+
+ u("#_addAudioAdditional").on("click", ".mb_tab a", async (e) => {
+ await switchTab(u(e.target).closest('.mb_tab').attr('data-name'))
})
-
- $(".ovk-diag-body").on("click", "input[name='addButton']", (e) => {
- $.ajax({
- type: "POST",
- url: `/audio${ev.target.dataset.id}/action?act=add_to_club`,
- data: {
- hash: u("meta[name=csrf]").attr("value"),
- club: document.querySelector("#addIconsWindow").value
- },
- beforeSend: () => {
- e.target.classList.add("lagged")
- document.querySelector(".errorPlace").innerHTML = ""
- },
- success: (response) => {
- if(!response.success)
- document.querySelector(".errorPlace").innerHTML = response.flash.message
-
- e.currentTarget.classList.remove("lagged")
- }
- })
+
+ u("#_addAudioAdditional").on("click", "input[name='add_to']", async (e) => {
+ if(u(`input[name='add_to']:checked`).length > 10) {
+ e.preventDefault()
+ }
})
})
$(document).on("click", ".musicIcon.add-icon", (e) => {
const id = e.currentTarget.dataset.id
- if(e.altKey) {
+ if(e.detail > 1 || e.altKey) {
const player = e.target.closest('.audioEmbed')
player.querySelector('.add-icon-group').click()
return
diff --git a/locales/en.strings b/locales/en.strings
index 707c7d11..9426e8ec 100644
--- a/locales/en.strings
+++ b/locales/en.strings
@@ -250,6 +250,8 @@
"clubs_posts" = "Group's posts";
"others_posts" = "Others posts";
+"show_more" = "Show more";
+
/* Friends */
"friends" = "Friends";
@@ -878,6 +880,7 @@
"my_playlists" = "My playlists";
"playlists" = "Playlists";
"audios_explicit" = "Contains obscene language";
+"audios_unlisted" = "Unlisted";
"withdrawn" = "Withdrawn";
"deleted" = "Deleted";
"owner" = "Owner";
@@ -901,7 +904,7 @@
"edit_playlist" = "Edit playlist";
"unable_to_load_queue" = "Error when loading queue.";
-"fully_delete_audio" = "Fully delete audio";
+"fully_delete_audio" = "Delete audio";
"attach_audio" = "Attach audio";
"detach_audio" = "Detach audio";
@@ -929,6 +932,12 @@
"listens_count_other" = "$1 listens";
"add_audio_to_club" = "Add audio to group";
+"add_audio" = "Adding audio";
+"add_audio_limitations" = "You can select up to 10 groups/playlists.";
+"to_club" = "To club";
+"to_playlist" = "To playlist";
+
+"audio_was_successfully_added" = "Audios was successfully added.";
"what_club_add" = "Which group do you want to add the song to?";
"group_has_audio" = "This group already has this song.";
"group_hasnt_audio" = "This group doesn't have this song.";
@@ -936,6 +945,7 @@
"by_name" = "by name";
"by_performer" = "by performer";
"no_access_clubs" = "There are no groups where you are an administrator.";
+"no_access_playlists" = "You haven't created any playlists.";
"audio_successfully_uploaded" = "Audio has been successfully uploaded and is currently being processed.";
"broadcast_audio" = "Broadcast audio to status";
@@ -948,6 +958,8 @@
"repeat_tip" = "Repeat";
"shuffle_tip" = "Shuffle";
"mute_tip" = "Mute";
+"playlist_hide_from_search" = "Unlisted";
+"confirm_deleting_audio" = "Do you sure want to delete this audio?";
/* Notifications */
@@ -1539,6 +1551,7 @@
"error_code" = "Error code: $1.";
"ffmpeg_timeout" = "Timed out waiting ffmpeg. Try to upload file again.";
"ffmpeg_not_installed" = "Failed to proccess the file. It looks like ffmpeg is not installed on this server.";
+"too_many_or_to_lack" = "Too few or too many sources.";
/* Admin actions */
@@ -2009,7 +2022,7 @@
"s_order_invert" = "Invert";
"s_order_by_reg_date" = "By reg date";
"s_order_by_creation_date" = "By creation date";
-"s_order_by_publishing_date" = "By publication date;
+"s_order_by_publishing_date" = "By publication date";
"s_order_by_upload_date" = "By upload date";
"s_by_date" = "By date";
diff --git a/locales/ru.strings b/locales/ru.strings
index 6984890c..4ea2e649 100644
--- a/locales/ru.strings
+++ b/locales/ru.strings
@@ -229,6 +229,7 @@
"users_posts" = "Записи $1";
"clubs_posts" = "Записи сообщества";
"others_posts" = "Чужие записи";
+"show_more" = "Показать больше";
/* Friends */
@@ -887,6 +888,13 @@
"listens_count_other" = "$1 прослушиваний";
"add_audio_to_club" = "Добавить аудио в группу";
+"add_audio" = "Добавление аудио";
+"add_audio_limitations" = "Можно выбрать до 10 групп/плейлистов.";
+"to_club" = "В группу";
+"to_playlist" = "В плейлист";
+
+"audio_was_successfully_added" = "Аудио были успешно добавлены.";
+
"what_club_add" = "В какую группу вы хотите добавить песню?";
"group_has_audio" = "У группы уже есть эта песня.";
"group_hasnt_audio" = "У группы нет этой песни.";
@@ -894,6 +902,7 @@
"by_name" = "по композициям";
"by_performer" = "по исполнителю";
"no_access_clubs" = "Нет групп, где вы являетесь администратором.";
+"no_access_playlists" = "Вы ещё не создавали плейлистов.";
"audio_successfully_uploaded" = "Аудио успешно загружено и на данный момент обрабатывается.";
"broadcast_audio" = "Транслировать аудио в статус";
@@ -1444,6 +1453,7 @@
"error_code" = "Код ошибки: $1.";
"ffmpeg_timeout" = "Превышено время ожидания обработки ffmpeg. Попробуйте загрузить файл снова.";
"ffmpeg_not_installed" = "Не удалось обработать файл. Похоже, на сервере не установлен ffmpeg.";
+"too_many_or_to_lack" = "Слишком мало либо слишком много источников.";
/* Admin actions */