BTHLABS-56: _Copy share link_ button in view association page

Co-authored-by: Tomek Wójcik <labs@tomekwojcik.pl>
Co-committed-by: Tomek Wójcik <labs@tomekwojcik.pl>
This commit is contained in:
Tomek Wójcik 2025-09-15 06:28:38 +00:00 committed by Tomek Wójcik
parent ab84f685c0
commit d1e60babf4
7 changed files with 113 additions and 11 deletions

View File

@ -0,0 +1,58 @@
/*!
* HotPocket by BTHLabs (https://hotpocket.app/)
* Copyright 2025-present BTHLabs <contact@bthlabs.pl> (https://bthlabs.pl/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
((HotPocket) => {
class HotPocketUIPasteboardLinkPlugin {
constructor (app) {
}
onLoad (event) {
let canCopy = false;
if (navigator.clipboard && navigator.clipboard.writeText) {
canCopy = true;
}
for (let pasteboardLink of document.querySelectorAll('.ui-pasteboard-link')) {
if (canCopy === false) {
pasteboardLink.classList.add('d-none');
} else {
pasteboardLink.addEventListener('click', this.onClick);
}
}
}
onClick = (event) => {
event.stopPropagation();
event.preventDefault();
const icon = event.target.querySelector('i.bi');
icon.classList.replace('bi-clipboard-fill', 'bi-clipboard');
navigator.clipboard.writeText(event.target.href).
then(() => {
icon.classList.replace('bi-clipboard', 'bi-clipboard-fill');
}).
catch((reason) => {
console.error('HotPocket.UI.PasteboardLink.onClick()', reason);
window.alert('Could not copy the link :(');
});
return false;
};
}
HotPocket.addPlugin('UI.PasteboardLink', (app) => {
return new HotPocketUIPasteboardLinkPlugin(app);
});
})(window.HotPocket);

View File

@ -41,7 +41,20 @@
if (navigator.share) {
shareButton.addEventListener('click', this.onShareButtonClick);
} else {
shareButton.addClass('d-none');
shareButton.remove();
}
}
const uiPlugin = this.app.plugin('UI');
for (let controlButton of document.querySelectorAll('#ViewAssociationView .ui-controls > a.btn')) {
if (uiPlugin.jsEnabled === true) {
if (controlButton.classList.contains('ui-noscript-show')) {
controlButton.remove();
}
} else {
if (controlButton.classList.contains('ui-noscript-hide')) {
controlButton.remove();
}
}
}
};
@ -59,12 +72,9 @@
onShareButtonClick = (event) => {
event.preventDefault();
const shareUrl = new URL(window.location.href);
shareUrl.searchParams.set('share', 'true');
navigator.share({
title: document.title,
url: shareUrl.toString(),
url: event.target.href,
});
return false;

View File

@ -18,6 +18,7 @@
class HotPocketUIPlugin {
constructor (app) {
document.body.classList.add('ui-js-enabled');
this.jsEnabled = document.body.classList.contains('ui-js-enabled');
if (window.navigator.standalone === true) {
document.querySelector('body').classList.add('ui-mode-standalone');

View File

@ -25,8 +25,8 @@
</a>
</p>
{% if show_controls %}
<p class="mb-3 text-center">
{% spaceless %}
<div class="d-flex justify-content-center mb-3">
<div class="btn-group ui-controls" role="group">
<a
class="btn btn-primary btn-sm"
href="{% url 'ui.associations.edit' pk=association.pk %}"
@ -35,14 +35,29 @@
<i class="bi bi-pencil"></i> {% translate 'Edit' %}
</a>
<a
class="btn btn-secondary btn-sm ms-2 ui-noscript-hide ui-share-button"
href="#"
class="btn btn-secondary btn-sm ui-noscript-hide ui-share-button"
href="{{ share_url }}"
role="button"
>
<i class="bi bi-box-arrow-up"></i> {% translate 'Share' %}
</a>
{% endspaceless %}
</p>
<a
class="btn btn-secondary btn-sm ui-noscript-hide ui-pasteboard-link"
href="{{ share_url }}"
role="button"
>
<i class="bi bi-clipboard"></i> {% translate 'Copy share link' %}
</a>
<a
class="btn btn-secondary btn-sm ui-noscript-show"
href="{{ share_url }}"
rel="noopener noreferer"
target="_blank"
>
<i class="bi bi-link-45deg"></i> {% translate 'Share link' %}
</a>
</div>
</div>
{% endif %}
{% if association.description %}
<div class="row mb-3">

View File

@ -152,6 +152,7 @@
<script src="{% static 'ui/js/hotpocket-backend.ui.ViewAssociationView.js' %}" type="text/javascript"></script>
<script src="{% static 'ui/js/hotpocket-backend.ui.BrowseAccountAppsView.js' %}" type="text/javascript"></script>
<script src="{% static 'ui/js/hotpocket-backend.ui.InlineCreateSaveForm.js' %}" type="text/javascript"></script>
<script src="{% static 'ui/js/hotpocket-backend.ui.PasteboardLink.js' %}" type="text/javascript"></script>
{% block page_scripts %}{% endblock %}
<script type="text/javascript">
(() => {

View File

@ -198,12 +198,21 @@ def view(request: HttpRequest, pk: uuid.UUID) -> HttpResponse:
if is_share is True:
show_controls = show_controls and False
share_url = reverse(
'ui.associations.view',
args=(association.pk,),
query=[
('share', 'true'),
],
)
return render(
request,
'ui/associations/view.html',
{
'association': association,
'show_controls': show_controls,
'share_url': share_url,
},
)

View File

@ -27,6 +27,14 @@ def test_authenticated_ok(authenticated_client: Client,
assert hasattr(result.context['association'], 'target') is True
assert result.context['association'].target.pk == association_out.target.pk
assert result.context['show_controls'] is True
assert 'share_url' in result.context
expected_share_url = reverse(
'ui.associations.view',
args=(association_out.pk,),
query=[('share', 'true')],
)
assert result.context['share_url'] == expected_share_url
@pytest.mark.django_db