diff --git a/accounts/views.py b/accounts/views.py index d737edbdd..f4976519d 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -55,6 +55,7 @@ from django.utils.http import int_to_base36 from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_exempt +from general.templatetags.absurl import url2absurl from oauth2_provider.models import AccessToken import tickets.views as TicketViews @@ -858,7 +859,9 @@ def attribution(request): @login_required def download_attribution(request): - content = {'csv': 'csv', 'txt': 'plain'} + content = {'csv': 'csv', + 'txt': 'plain', + 'json': 'json'} qs_sounds = Download.objects.annotate(download_type=Value('sound', CharField()))\ .values('download_type', 'sound_id', 'sound__user__username', 'sound__original_filename', @@ -871,9 +874,9 @@ def download_attribution(request): qs = qs_sounds.union(qs_packs).order_by('-created') download = request.GET.get('dl', '') + now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + filename = f'{request.user}_{now}_attribution.{download}' if download in ['csv', 'txt']: - now = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') - filename = f'{request.user}_{now}_attribution.{download}' response = HttpResponse(content_type=f'text/{content[download]}') response['Content-Disposition'] = f'attachment; filename="{filename}"' output = io.StringIO() @@ -896,6 +899,32 @@ def download_attribution(request): row['created'])) response.writelines(output.getvalue()) return response + elif download == 'json': + output = [] + for row in qs: + if row['download_type'][0].upper() == 'S': + output.append({ + 'sound_url': url2absurl(reverse("sound", args=[row['sound__user__username'], row['sound_id']])), + 'sound_name': row['sound__original_filename'], + 'author_url': url2absurl(reverse("account", args=[row['sound__user__username']])), + 'author_name': row['sound__user__username'], + 'license_url': row['license__deed_url'] or row['sound__license__deed_url'], + 'license_name': license_with_version(row['license__name'] or row['sound__license__name'], + row['license__deed_url'] or row['sound__license__deed_url']), + 'timestamp': str(row['created']) + }) + elif row['download_type'][0].upper() == 'P': + output.append({ + 'pack_url': url2absurl(reverse("pack", args=[row['sound__user__username'], row['sound_id']])), + 'pack_name': row['sound__original_filename'], + 'author_url': url2absurl(reverse("account", args=[row['sound__user__username']])), + 'author_name': row['sound__user__username'], + 'license_url': row['license__deed_url'] or row['sound__license__deed_url'], + 'license_name': license_with_version(row['license__name'] or row['sound__license__name'], + row['license__deed_url'] or row['sound__license__deed_url']), + 'timestamp': str(row['created']) + }) + return JsonResponse(output, safe=False) else: return HttpResponseRedirect(reverse('accounts-attribution')) diff --git a/freesound/static/bw-frontend/src/components/modal.js b/freesound/static/bw-frontend/src/components/modal.js index 4b54bbd57..32738a00a 100644 --- a/freesound/static/bw-frontend/src/components/modal.js +++ b/freesound/static/bw-frontend/src/components/modal.js @@ -160,6 +160,12 @@ const handleGenericModal = (fetchContentUrl, onLoadedCallback, onClosedCallback, if (onLoadedCallback !== undefined){ onLoadedCallback(modalContainer); } + + // Trigger modal loaded event in case it should be used by other components + const event = new CustomEvent("modalLoaded", { + detail: {fetchContentUrl, modalContainer}, + }); + document.dispatchEvent(event); // If modal is activated with a param, add the param to the URL when opening the modal if (modalActivationParam !== undefined){ diff --git a/freesound/static/bw-frontend/src/components/select.js b/freesound/static/bw-frontend/src/components/select.js index f6734f749..550b562a7 100644 --- a/freesound/static/bw-frontend/src/components/select.js +++ b/freesound/static/bw-frontend/src/components/select.js @@ -21,7 +21,7 @@ function makeSelect(container) { selectElement.style.display = 'none'; const wrapper = wrapElement( - document.getElementById(selectElement.id), + selectElement, document.createElement('div'), select_i, selected_index_text diff --git a/freesound/static/bw-frontend/src/pages/sound.js b/freesound/static/bw-frontend/src/pages/sound.js index 53b03bdd4..282fcfb13 100644 --- a/freesound/static/bw-frontend/src/pages/sound.js +++ b/freesound/static/bw-frontend/src/pages/sound.js @@ -1,8 +1,8 @@ import './page-polyfills'; -import {showToast} from '../components/toast'; -import {playAtTime} from '../components/player/utils'; -import {handleGenericModalWithForm} from '../components/modal'; -import {addRecaptchaScriptTagToMainHead} from '../utils/recaptchaDynamicReload' +import { showToast } from '../components/toast'; +import { playAtTime } from '../components/player/utils'; +import { handleGenericModalWithForm, dismissModal } from '../components/modal'; +import { addRecaptchaScriptTagToMainHead } from '../utils/recaptchaDynamicReload' import { prepareAfterDownloadSoundModals } from '../components/afterDownloadModal.js'; const toggleEmbedCodeElement = document.getElementById('toggle-embed-code'); @@ -17,20 +17,23 @@ const urlParams = new URLSearchParams(window.location.search); prepareAfterDownloadSoundModals(); +const copyFromInputElement = (inputElement) => { + inputElement.select(); + inputElement.setSelectionRange(0, 99999); + inputElement.execCommand("copy"); + inputElement.getSelection().removeAllRanges(); +} + + const copyShareUrlToClipboard = (useFileURL) => { var shareLinkInputElement = shareLinkElement.getElementsByTagName("input")[0]; - console.log(shareLinkElement.dataset.staticFileUrl - , shareLinkElement.dataset.soundPageUrl) if (useFileURL) { shareLinkInputElement.value = shareLinkElement.dataset.staticFileUrl; } else { shareLinkInputElement.value = shareLinkElement.dataset.soundPageUrl; } - shareLinkInputElement.select(); - shareLinkInputElement.setSelectionRange(0, 99999); - document.execCommand("copy"); - showToast('Sound URL copied in the clipboard'); - document.getSelection().removeAllRanges(); + copyFromInputElement(shareLinkInputElement); + showToast('Sound URL copied to the clipboard'); } const toggleEmbedCode = () => { @@ -143,3 +146,28 @@ if (flagSoundModalParamValue) { flagSoundButton.addEventListener('click', (evt) => { handleFlagSoundModal(); }) + + +// Attribution modal +const handleAttributionModal = (modalContainer) => { + const selectElement = modalContainer.getElementsByTagName('select')[0]; + const textArea = modalContainer.getElementsByTagName('textarea')[0]; + textArea.value = selectElement.value; + selectElement.addEventListener('change', () => { + textArea.value = selectElement.value; + }); + + const buttons = modalContainer.getElementsByTagName('button'); + const copyButton = buttons[buttons.length - 1]; + copyButton.addEventListener('click', () => { + copyFromInputElement(textArea); + showToast('Attribution text copied to the clipboard'); + dismissModal(modalContainer.id); + }); +} + +document.addEventListener('modalLoaded', (evt) => { + if (evt.detail.modalContainer.id === 'soundAttributionModal') { + handleAttributionModal(evt.detail.modalContainer); + } +}); \ No newline at end of file diff --git a/sounds/models.py b/sounds/models.py index 10f139e02..514d3b661 100644 --- a/sounds/models.py +++ b/sounds/models.py @@ -53,6 +53,7 @@ from comments.models import Comment from freesound.celery import app as celery_app from general import tasks +from general.templatetags.absurl import url2absurl from geotags.models import GeoTag from ratings.models import SoundRating from general.templatetags.util import formatnumber @@ -928,6 +929,21 @@ def get_license_history(self): """ return [(slh.created, slh.license) for slh in self.soundlicensehistory_set.select_related('license').order_by('-created')] + + @cached_property + def attribution_texts(self): + return { + 'plain_text': f'{self.original_filename} by {self.user.username} -- {url2absurl(reverse("short-sound-link", args=[self.id]))} -- License: {self.license.name_with_version}', + 'html': f'{self.original_filename} by {self.user.username} | License: {self.license.name_with_version}', + 'json': json.dumps({ + 'sound_url': url2absurl(self.get_absolute_url()), + 'sound_name': self.original_filename, + 'author_url': url2absurl(reverse("account", args=[self.user.username])), + 'author_name': self.user.username, + 'license_url': self.license.deed_url, + 'license_name': self.license.name_with_version, + }) + } def get_sound_tags(self, limit=None): """ diff --git a/templates/accounts/attribution.html b/templates/accounts/attribution.html index 1646a3118..b146aa906 100644 --- a/templates/accounts/attribution.html +++ b/templates/accounts/attribution.html @@ -11,7 +11,7 @@ This is the list of files you have downloaded. When you use freesound samples under the Attribution or Attribution NonCommercial license, you have to credit the original creator of the sound in your work. This list makes it easy to do so. "S" means sound, "P" means pack. There are 3 flavors of this list: regular, html or plain text. - Alternatively, you can download the complete record of your downloaded sounds in csv or plain text formats. + Alternatively, you can download the complete record of your downloaded sounds in csv, plain text, or json formats.
@@ -26,7 +26,7 @@