mirror of
https://github.com/openvk/openvk
synced 2025-01-22 07:44:27 +03:00
picker
This commit is contained in:
parent
d18c2bba6e
commit
49f8238670
9 changed files with 227 additions and 92 deletions
|
@ -34,6 +34,30 @@ final class DocumentsPresenter extends OpenVKPresenter
|
||||||
$order = in_array($current_order, [0,1,2]) ? $current_order : 0;
|
$order = in_array($current_order, [0,1,2]) ? $current_order : 0;
|
||||||
$tab = in_array($current_tab, [0,1,2,3,4,5,6,7,8]) ? $current_tab : 0;
|
$tab = in_array($current_tab, [0,1,2,3,4,5,6,7,8]) ? $current_tab : 0;
|
||||||
|
|
||||||
|
$api_request = $this->queryParam("picker") == "1";
|
||||||
|
if($api_request && $_SERVER["REQUEST_METHOD"] === "POST") {
|
||||||
|
$ctx_type = $this->postParam("context");
|
||||||
|
$docs = NULL;
|
||||||
|
|
||||||
|
switch($ctx_type) {
|
||||||
|
default:
|
||||||
|
case "list":
|
||||||
|
$docs = (new Documents)->getDocumentsByOwner($owner_id, (int)$order, (int)$tab);
|
||||||
|
break;
|
||||||
|
case "search":
|
||||||
|
$ctx_query = $this->postParam("ctx_query");
|
||||||
|
$docs = (new Documents)->find($ctx_query);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->template->docs = $docs->page($page, OPENVK_DEFAULT_PER_PAGE);
|
||||||
|
$this->template->page = $page;
|
||||||
|
$this->template->count = $docs->size();
|
||||||
|
$this->template->pagesCount = ceil($this->template->count / OPENVK_DEFAULT_PER_PAGE);
|
||||||
|
$this->template->_template = "Documents/ApiGetContext.xml";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$docs = (new Documents)->getDocumentsByOwner($owner_id, (int)$order, (int)$tab);
|
$docs = (new Documents)->getDocumentsByOwner($owner_id, (int)$order, (int)$tab);
|
||||||
$this->template->tabs = (new Documents)->getTypes($owner_id);
|
$this->template->tabs = (new Documents)->getTypes($owner_id);
|
||||||
$this->template->tags = (new Documents)->getTags($owner_id, (int)$tab);
|
$this->template->tags = (new Documents)->getTags($owner_id, (int)$tab);
|
||||||
|
|
9
Web/Presenters/templates/Documents/ApiGetContext.xml
Normal file
9
Web/Presenters/templates/Documents/ApiGetContext.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<input type="hidden" name="count" value="{$count}">
|
||||||
|
<input type="hidden" name="pagesCount" value="{$pagesCount}">
|
||||||
|
<input type="hidden" name="page" value="{$page}">
|
||||||
|
|
||||||
|
{foreach $docs as $doc}
|
||||||
|
<div class='display_flex_row _content' data-attachmentdata="{$doc->getVirtualId()}_{$doc->getId()}_{$doc->getAccessKey()}" data-name='{$doc->getName()}'>
|
||||||
|
{include "components/doc.xml", doc => $doc, hideButtons => true}
|
||||||
|
</div>
|
||||||
|
{/foreach}
|
|
@ -67,8 +67,8 @@
|
||||||
</div>
|
</div>
|
||||||
{elseif $attachment instanceof \openvk\Web\Models\Entities\Document}
|
{elseif $attachment instanceof \openvk\Web\Models\Entities\Document}
|
||||||
<div style="width:100%;">
|
<div style="width:100%;">
|
||||||
<div style="display:none" data-att_type='doc' data-att_id="{$attachment->getPrettyId()}">
|
<div style="display:none" data-att_type="doc" data-att_id="{$attachment->getPrettiestId()}">
|
||||||
<div class="docMainItem attachment_doc attachment_note" data-id="{$attachment->getPrettiestId()}">
|
<div class="docMainItem attachment_doc attachment_note">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 10"><polygon points="0 0 0 10 8 10 8 4 4 4 4 0 0 0"/><polygon points="5 0 5 3 8 3 5 0"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 10"><polygon points="0 0 0 10 8 10 8 4 4 4 4 0 0 0"/><polygon points="5 0 5 3 8 3 5 0"/></svg>
|
||||||
|
|
||||||
<div class='attachment_note_content'>
|
<div class='attachment_note_content'>
|
||||||
|
|
|
@ -86,14 +86,14 @@
|
||||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/audio-ac3.png" />
|
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/audio-ac3.png" />
|
||||||
{_audio}
|
{_audio}
|
||||||
</a>
|
</a>
|
||||||
|
<a n:if="$docs ?? true" id="__documentAttachment" {if !is_null($club ?? NULL) && $club->canBeModifiedBy($thisUser)}data-club="{$club->getRealId()}"{/if}>
|
||||||
|
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-octet-stream.png" />
|
||||||
|
{_document}
|
||||||
|
</a>
|
||||||
<a n:if="$notes ?? false" id="__notesAttachment">
|
<a n:if="$notes ?? false" id="__notesAttachment">
|
||||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-srt.png" />
|
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-x-srt.png" />
|
||||||
{_note}
|
{_note}
|
||||||
</a>
|
</a>
|
||||||
<a n:if="$docs ?? false" id="__documentAttachment">
|
|
||||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/mimetypes/application-octet-stream.png" />
|
|
||||||
{_document}
|
|
||||||
</a>
|
|
||||||
<a n:if="$graffiti ?? false" onclick="initGraffiti(event);">
|
<a n:if="$graffiti ?? false" onclick="initGraffiti(event);">
|
||||||
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
|
<img src="/assets/packages/static/openvk/img/oxygen-icons/16x16/actions/draw-brush.png" />
|
||||||
{_graffiti}
|
{_graffiti}
|
||||||
|
|
|
@ -2711,6 +2711,10 @@ a.poll-retract-vote {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ovk-photo-view .photo_viewer_wrapper.photo_viewer_wrapper_scrollable {
|
||||||
|
height: unset;
|
||||||
|
}
|
||||||
|
|
||||||
.ovk-photo-view #ovk-photo-img {
|
.ovk-photo-view #ovk-photo-img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 80vh;
|
max-height: 80vh;
|
||||||
|
@ -4107,7 +4111,7 @@ hr {
|
||||||
border-bottom: unset !important;
|
border-bottom: unset !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.docListViewItem:hover {
|
.docListViewItem:hover, .attachButton:hover {
|
||||||
background: #f7f7f7;
|
background: #f7f7f7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4219,7 +4223,18 @@ hr {
|
||||||
|
|
||||||
.attachments .docGalleryItem {
|
.attachments .docGalleryItem {
|
||||||
display: block;
|
display: block;
|
||||||
min-width: 200px;
|
min-width: 130px;
|
||||||
|
height: 150px;
|
||||||
width: 50%;
|
width: 50%;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachButton {
|
||||||
|
width: 12%;
|
||||||
|
height: 55px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
|
@ -304,112 +304,190 @@ u(document).on("click", ".docListViewItem a.viewerOpener, a.docGalleryItem", asy
|
||||||
CMessageBox.toggleLoader()
|
CMessageBox.toggleLoader()
|
||||||
})
|
})
|
||||||
|
|
||||||
u(document).on('click', '#__documentAttachment', async (e) => {
|
// ctx > "wall" and maybe "messages" in future
|
||||||
|
// source > "user" || "club" > source_id
|
||||||
|
async function __docAttachment(form, ctx = "wall", source = "user", source_id = 0) {
|
||||||
const per_page = 10
|
const per_page = 10
|
||||||
const form = u(e.target).closest('form')
|
|
||||||
const msg = new CMessageBox({
|
const msg = new CMessageBox({
|
||||||
title: tr('select_doc'),
|
title: tr('select_doc'),
|
||||||
body: `
|
custom_template: u(`
|
||||||
<div class='attachment_selector'>
|
<div class="ovk-photo-view-dimmer">
|
||||||
<div id='attachment_insert' style='height: 325px;'>
|
<div class="ovk-photo-view" style="z-index: 1025;">
|
||||||
<div class="docsInsert"></div>
|
<div class="photo_com_title">
|
||||||
</div>
|
<text id="photo_com_title_photos">
|
||||||
</div>
|
${tr("select_doc")}
|
||||||
`,
|
</text>
|
||||||
buttons: [tr('close')],
|
${source != "user" ?
|
||||||
callbacks: [Function.noop]
|
`
|
||||||
|
<a id="_doc_picker_go_to_my">
|
||||||
|
${tr("go_to_my_documents")}
|
||||||
|
</a>
|
||||||
|
`
|
||||||
|
: ""}
|
||||||
|
<div>
|
||||||
|
<a id="ovk-photo-close">${tr("close")}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='photo_viewer_wrapper photo_viewer_wrapper_scrollable doc_viewer_wrapper'>
|
||||||
|
<div class='attachment_selector' style="width: 100%;">
|
||||||
|
<div id='_attachment_insert'>
|
||||||
|
<div class="docsInsert"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ovk-photo-details"></div>
|
||||||
|
</div>
|
||||||
|
</div>`),
|
||||||
})
|
})
|
||||||
|
|
||||||
msg.getNode().attr('style', 'width: 340px;')
|
msg.getNode().find(".ovk-photo-view").attr('style', 'width: 400px;')
|
||||||
msg.getNode().find('.ovk-diag-body').attr('style', 'height:335px;padding:0px;')
|
msg.getNode().find('.ovk-diag-body').attr('style', 'height:335px;padding:0px;')
|
||||||
|
docs_reciever = new class {
|
||||||
async function __recieveDocs(page) {
|
ctx = "my"
|
||||||
u('#gif_loader').remove()
|
ctx_id = 0
|
||||||
u('#attachment_insert').append(`<div id='gif_loader'></div>`)
|
stat = {
|
||||||
const insert_place = u('#attachment_insert .docsInsert')
|
page: 0,
|
||||||
let docs = null
|
pagesCount: 0,
|
||||||
|
count: 0,
|
||||||
try {
|
|
||||||
docs = await window.OVKAPI.call('docs.get', {'owner_id': window.openvk.current_id, 'count': per_page, 'offset': per_page * page})
|
|
||||||
} catch(e) {
|
|
||||||
u("#gif_loader").remove()
|
|
||||||
insert_place.html("Err")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u("#gif_loader").remove()
|
clean() {
|
||||||
const pages_count = Math.ceil(Number(docs.count) / per_page)
|
this.stat = {
|
||||||
|
page: 0,
|
||||||
|
pagesCount: 0,
|
||||||
|
count: 0,
|
||||||
|
}
|
||||||
|
|
||||||
if(docs.count < 1) {
|
u('#gif_loader').remove()
|
||||||
insert_place.append(tr('no_docs'))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
docs.items.forEach(doc => {
|
async page(page = 1, perPage = 10) {
|
||||||
is_attached = (form.find(`.upload-item[data-type='doc'][data-id='${doc.owner_id}_${doc.id}']`)).length > 0
|
u('#_attachment_insert').append(`<div id='gif_loader'></div>`)
|
||||||
insert_place.append(`
|
|
||||||
<div class='display_flex_row _content' data-attachmentdata="${doc.owner_id}_${doc.id}_${doc.access_key}" data-name='${escapeHtml(doc.title)}'>
|
const fd = new FormData
|
||||||
<div class="attachDoc" id='__attach_doc'>
|
fd.append("context", "list")
|
||||||
<span>${is_attached ? tr("detach") : tr("attach")}</span>
|
fd.append("hash", window.router.csrf)
|
||||||
|
const req = await fetch(`/docs${source == "club" ? source_id : ""}?picker=1&p=${page}`, {
|
||||||
|
method: "POST",
|
||||||
|
body: fd
|
||||||
|
})
|
||||||
|
const res = await req.text()
|
||||||
|
const dom = new DOMParser
|
||||||
|
const pre = dom.parseFromString(res, "text/html")
|
||||||
|
const pagesCount = Number(pre.querySelector("input[name='pagesCount']").value)
|
||||||
|
const count = Number(pre.querySelector("input[name='count']").value)
|
||||||
|
if(count < 1) {
|
||||||
|
u('#_attachment_insert .docsInsert').append(`
|
||||||
|
<div class="information">
|
||||||
|
${tr("no_documents")}.
|
||||||
</div>
|
</div>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
pre.querySelectorAll("._content").forEach(doc => {
|
||||||
|
const res = u(`${doc.outerHTML}`)
|
||||||
|
const id = res.attr("data-attachmentdata")
|
||||||
|
|
||||||
|
res.find(".docMainItem").attr("style", "width: 85%;")
|
||||||
|
res.append(`
|
||||||
|
<div class="attachButton" id='__attach_doc'>
|
||||||
|
<span>${this.isDocAttached(id) ? tr("detach") : tr("attach")}</span>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
})
|
u('#_attachment_insert .docsInsert').append(res)
|
||||||
|
})
|
||||||
|
|
||||||
if(page < pages_count - 1) {
|
this.stat.page = page
|
||||||
insert_place.append(`
|
this.stat.pagesCount = pagesCount
|
||||||
<div id="show_more" data-pagesCount="${pages_count}" data-page="${page + 1}">
|
this.stat.count = count
|
||||||
<span>${tr('show_more')}</span>
|
u('#gif_loader').remove()
|
||||||
</div>`)
|
this.showMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
showMore() {
|
||||||
|
if(this.stat.page < this.stat.pagesCount) {
|
||||||
|
u('#_attachment_insert').append(`
|
||||||
|
<div id="show_more" data-pagesCount="${this.stat.pagesCount}">
|
||||||
|
<span>${tr('show_more')}</span>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
maxAttachmentsCheck() {
|
||||||
|
if(u(form).find(`.upload-item`).length > window.openvk.max_attachments) {
|
||||||
|
makeError(tr('too_many_attachments'), 'Red', 10000, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
attach(dataset, button) {
|
||||||
|
if(this.isDocAttached(dataset.attachmentdata)) {
|
||||||
|
(form.find(`.upload-item[data-type='doc'][data-id='${dataset.attachmentdata}']`)).remove()
|
||||||
|
button.html(tr('attach'))
|
||||||
|
} else {
|
||||||
|
const _url = dataset.attachmentdata.split("_")
|
||||||
|
button.html(tr('detach'))
|
||||||
|
form.find('.post-vertical').append(`
|
||||||
|
<div class="vertical-attachment upload-item" draggable="true" data-type='doc' data-id="${dataset.attachmentdata}">
|
||||||
|
<div class='vertical-attachment-content' draggable="false">
|
||||||
|
<div class="docMainItem attachment_doc attachment_note">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 10"><polygon points="0 0 0 10 8 10 8 4 4 4 4 0 0 0"/><polygon points="5 0 5 3 8 3 5 0"/></svg>
|
||||||
|
|
||||||
|
<div class='attachment_note_content'>
|
||||||
|
<span class="attachment_note_text">${tr("document")}</span>
|
||||||
|
<span class="attachment_note_name"><a href="/doc${_url[0]}_${_url[1]}?key=${_url[2]}">${ovk_proc_strtr(escapeHtml(dataset.name), 50)}</a></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='vertical-attachment-remove'>
|
||||||
|
<div id='small_remove_button'></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isDocAttached(attachmentdata) {
|
||||||
|
return (form.find(`.upload-item[data-type='doc'][data-id='${attachmentdata}']`)).length > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// next page
|
msg.getNode().find("#ovk-photo-close").on("click", function(e) {
|
||||||
u(".ovk-diag-body .attachment_selector").on("click", "#show_more", async (ev) => {
|
msg.close()
|
||||||
const target = u(ev.target).closest('#show_more')
|
|
||||||
target.addClass('lagged')
|
|
||||||
await __recieveDocs(Number(target.nodes[0].dataset.page))
|
|
||||||
target.remove()
|
|
||||||
})
|
})
|
||||||
|
msg.getNode().on("click", "#__attach_doc", async (ev) => {
|
||||||
u(".ovk-diag-body .attachment_selector").on("click", "#__attach_doc", async (ev) => {
|
if(docs_reciever.maxAttachmentsCheck() == true) {
|
||||||
if(u(form).find(`.upload-item`).length > window.openvk.max_attachments) {
|
return
|
||||||
makeError(tr('too_many_attachments'), 'Red', 10000, 1)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = u(ev.target).closest('._content')
|
const target = u(ev.target).closest('._content')
|
||||||
const button = target.find('#__attach_doc')
|
const button = target.find('#__attach_doc')
|
||||||
const dataset = target.nodes[0].dataset
|
const dataset = target.nodes[0].dataset
|
||||||
const is_attached = (form.find(`.upload-item[data-type='doc'][data-id='${dataset.attachmentdata}']`)).length > 0
|
docs_reciever.attach(dataset, button)
|
||||||
if(is_attached) {
|
})
|
||||||
(form.find(`.upload-item[data-type='doc'][data-id='${dataset.attachmentdata}']`)).remove()
|
msg.getNode().on("click", "#show_more", async (ev) => {
|
||||||
button.html(tr('attach'))
|
const target = u(ev.target).closest('#show_more')
|
||||||
} else {
|
target.addClass('lagged')
|
||||||
if(form.find(`.upload-item`).length + 1 > window.openvk.max_attachments) {
|
await docs_reciever.page(docs_reciever.stat.page + 1)
|
||||||
makeError(tr('too_many_attachments'), 'Red', 10000, 1)
|
target.remove()
|
||||||
return
|
})
|
||||||
}
|
msg.getNode().on("click", "#_doc_picker_go_to_my", async (e) => {
|
||||||
|
msg.close()
|
||||||
button.html(tr('detach'))
|
await __docAttachment(form, "wall")
|
||||||
form.find('.post-vertical').append(`
|
|
||||||
<div class="vertical-attachment upload-item" draggable="true" data-type='doc' data-id="${dataset.attachmentdata}">
|
|
||||||
<div class='vertical-attachment-content' draggable="false">
|
|
||||||
<div class="docMainItem attachment_doc attachment_note">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 10"><polygon points="0 0 0 10 8 10 8 4 4 4 4 0 0 0"/><polygon points="5 0 5 3 8 3 5 0"/></svg>
|
|
||||||
|
|
||||||
<div class='attachment_note_content'>
|
|
||||||
<span class="attachment_note_text">${tr("document")}</span>
|
|
||||||
<span class="attachment_note_name"><a href="/doc${dataset.attachmentdata}">${ovk_proc_strtr(escapeHtml(dataset.name), 66)}</a></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class='vertical-attachment-remove'>
|
|
||||||
<div id='small_remove_button'></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
__recieveDocs(0)
|
await docs_reciever.page(docs_reciever.stat.page + 1)
|
||||||
|
}
|
||||||
|
u(document).on('click', '#__documentAttachment', async (e) => {
|
||||||
|
const form = u(e.target).closest('form')
|
||||||
|
const targ = u(e.target).closest("#__documentAttachment")
|
||||||
|
let entity_source = "user"
|
||||||
|
let entity_id = 0
|
||||||
|
if(targ.attr('data-club') != null) {
|
||||||
|
entity_source = "club"
|
||||||
|
entity_id = Number(targ.attr('data-club'))
|
||||||
|
}
|
||||||
|
|
||||||
|
await __docAttachment(form, "wall", entity_source, entity_id)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1039,7 +1039,10 @@ u(document).on("click", "#editPost", async (e) => {
|
||||||
// horizontal attachments
|
// horizontal attachments
|
||||||
api_post.attachments.forEach(att => {
|
api_post.attachments.forEach(att => {
|
||||||
const type = att.type
|
const type = att.type
|
||||||
const aid = att[type].owner_id + '_' + att[type].id
|
let aid = att[type].owner_id + '_' + att[type].id
|
||||||
|
if(att[type] && att[type].access_key) {
|
||||||
|
aid += "_" + att[type].access_key
|
||||||
|
}
|
||||||
|
|
||||||
if(type == 'video' || type == 'photo') {
|
if(type == 'video' || type == 'photo') {
|
||||||
let preview = ''
|
let preview = ''
|
||||||
|
|
|
@ -2381,3 +2381,6 @@
|
||||||
"documents_sort_add" = "By date";
|
"documents_sort_add" = "By date";
|
||||||
"documents_sort_alphabet" = "A-Z";
|
"documents_sort_alphabet" = "A-Z";
|
||||||
"documents_sort_size" = "By size";
|
"documents_sort_size" = "By size";
|
||||||
|
"select_doc" = "Attach document";
|
||||||
|
"no_documents" = "No documents found";
|
||||||
|
"go_to_my_documents" = "Go to own documents";
|
||||||
|
|
|
@ -2276,3 +2276,6 @@
|
||||||
"documents_sort_add" = "По дате добавления";
|
"documents_sort_add" = "По дате добавления";
|
||||||
"documents_sort_alphabet" = "A-Z/А-Я";
|
"documents_sort_alphabet" = "A-Z/А-Я";
|
||||||
"documents_sort_size" = "По размеру";
|
"documents_sort_size" = "По размеру";
|
||||||
|
"select_doc" = "Выбор документа";
|
||||||
|
"no_documents" = "Документов нет";
|
||||||
|
"go_to_my_documents" = "Перейти к своим документам";
|
||||||
|
|
Loading…
Reference in a new issue