diff --git a/accounts/forms.py b/accounts/forms.py index 1ef678282..a5a9ae5ec 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -592,8 +592,8 @@ def get_users(self, username_or_email): return (u for u in active_users) def save(self, domain_override=None, - subject_template_name='registration/password_reset_subject.txt', - email_template_name='registration/password_reset_email.html', + subject_template_name='emails/password_reset_subject.txt', + email_template_name='emails/password_reset_email.html', use_https=False, token_generator=default_token_generator, from_email=None, request=None, html_email_template_name=None, extra_email_context=None): diff --git a/accounts/tests/test_profile.py b/accounts/tests/test_profile.py index f6a728a6c..d2c0760cf 100644 --- a/accounts/tests/test_profile.py +++ b/accounts/tests/test_profile.py @@ -98,19 +98,19 @@ def test_handle_uploaded_image(self): def test_edit_user_profile(self): user = User.objects.create_user("testuser") self.client.force_login(user) - self.client.post("/home/edit/", { + resp = self.client.post("/home/edit/", { 'profile-home_page': 'http://www.example.com/', 'profile-username': 'testuser', 'profile-about': 'About test text', 'profile-signature': 'Signature test text', - 'profile-not_shown_in_online_users_list': True, + 'profile-ui_theme_preference': 'd', }) user = User.objects.select_related('profile').get(username="testuser") self.assertEqual(user.profile.home_page, 'http://www.example.com/') self.assertEqual(user.profile.about, 'About test text') self.assertEqual(user.profile.signature, 'Signature test text') - self.assertEqual(user.profile.not_shown_in_online_users_list, True) + self.assertEqual(user.profile.ui_theme_preference, 'd') # Now we change the username the maximum allowed times for i in range(settings.USERNAME_CHANGE_MAX_TIMES): @@ -119,7 +119,7 @@ def test_edit_user_profile(self): 'profile-username': 'testuser%d' % i, 'profile-about': 'About test text', 'profile-signature': 'Signature test text', - 'profile-not_shown_in_online_users_list': True, + 'profile-ui_theme_preference': 'd', }) user.refresh_from_db() @@ -133,7 +133,7 @@ def test_edit_user_profile(self): 'profile-username': 'testuser-error', 'profile-about': 'About test text', 'profile-signature': 'Signature test text', - 'profile-not_shown_in_online_users_list': True, + 'profile-ui_theme_preference': 'd', }) user.refresh_from_db() self.assertEqual(user.old_usernames.count(), settings.USERNAME_CHANGE_MAX_TIMES) diff --git a/accounts/tests/test_upload.py b/accounts/tests/test_upload.py index 83ea99b97..4f060facb 100644 --- a/accounts/tests/test_upload.py +++ b/accounts/tests/test_upload.py @@ -33,7 +33,7 @@ from tags.models import Tag from utils.filesystem import File from utils.test_helpers import create_test_files, override_uploads_path_with_temp_directory, \ - override_csv_path_with_temp_directory, create_user_and_sounds, test_using_bw_ui + override_csv_path_with_temp_directory, create_user_and_sounds class UserUploadAndDescribeSounds(TestCase): @@ -71,110 +71,41 @@ def test_select_uploaded_files_to_describe(self): create_test_files(filenames, user_upload_path) # Check that files are displayed in the template - resp = self.client.get('/home/describe/') + resp = self.client.get(reverse('accounts-manage-sounds', args=['pending_description'])) self.assertEqual(resp.status_code, 200) self.assertListEqual(sorted([os.path.basename(f.full_path) for f in resp.context['file_structure'].children]), sorted(filenames)) # Selecting one file redirects to /home/describe/sounds/ sounds_to_describe_idx = [0] - resp = self.client.post('/home/describe/', { - 'describe': ['Describe selected files'], + resp = self.client.post(reverse('accounts-manage-sounds', args=['pending_description']), { + 'describe': 'describe', 'sound-files': [f'file{idx}' for idx in sounds_to_describe_idx], # Note this is not the filename but the value of the "select" option }) - self.assertRedirects(resp, '/home/describe/sounds/') + self.assertRedirects(resp, reverse('accounts-describe-sounds')) self.assertEqual(self.client.session['len_original_describe_edit_sounds'], len(sounds_to_describe_idx)) self.assertListEqual(sorted([os.path.basename(f.full_path) for f in self.client.session['describe_sounds']]), sorted([filenames[idx] for idx in sounds_to_describe_idx])) # Selecting multiple file redirects to /home/describe/license/ sounds_to_describe_idx = [1, 2, 3] - resp = self.client.post('/home/describe/', { - 'describe': ['Describe selected files'], + resp = self.client.post(reverse('accounts-manage-sounds', args=['pending_description']), { + 'describe': 'describe', 'sound-files': [f'file{idx}' for idx in sounds_to_describe_idx], # Note this is not the filename but the value of the "select" option }) - self.assertRedirects(resp, '/home/describe/license/') + self.assertRedirects(resp, reverse('accounts-describe-license')) self.assertEqual(self.client.session['len_original_describe_edit_sounds'], len(sounds_to_describe_idx)) self.assertListEqual(sorted([os.path.basename(f.full_path) for f in self.client.session['describe_sounds']]), sorted([filenames[idx] for idx in sounds_to_describe_idx])) - # Selecting files to delete, redirect to delete confirmation + # Selecting files to delete, delete the files sounds_to_delete_idx = [1, 2, 3] - resp = self.client.post('/home/describe/', { - 'delete': ['Delete selected files'], + resp = self.client.post(reverse('accounts-manage-sounds', args=['pending_description']), { + 'delete_confirm': 'delete_confirm', 'sound-files': [f'file{idx}' for idx in sounds_to_delete_idx], # Note this is not the filename but the value of the "select" option, }) - self.assertEqual(resp.status_code, 200) - self.assertListEqual(sorted(resp.context['filenames']), sorted([filenames[idx] for idx in sounds_to_describe_idx])) - - # Selecting confirmation of files to delete - resp = self.client.post('/home/describe/', { - 'delete_confirm': ['delete_confirm'], - 'sound-files': [f'file{idx}' for idx in sounds_to_delete_idx], # Note this is not the filename but the value of the "select" option, - }) - self.assertRedirects(resp, '/home/describe/') + self.assertRedirects(resp, reverse('accounts-manage-sounds', args=['pending_description'])) self.assertEqual(len(os.listdir(user_upload_path)), len(filenames) - len(sounds_to_delete_idx)) @override_uploads_path_with_temp_directory def test_describe_selected_files(self): - # Create audio files - filenames = ['file1.wav', 'filè2.wav'] - user = User.objects.create_user("testuser", password="testpass") - existing_pack = Pack.objects.create(user=user, name="existing pack") - # Set is_whitelisted because this will trigger change_moderation_state after creating sound and make - # the test more complete - user.profile.is_whitelisted = True - user.profile.save() - self.client.force_login(user) - user_upload_path = settings.UPLOADS_PATH + '/%i/' % user.id - os.makedirs(user_upload_path, exist_ok=True) - create_test_files(filenames, user_upload_path) - - # Set license and pack data in session - session = self.client.session - session['describe_license'] = License.objects.all()[0] - session['describe_pack'] = False - session['describe_sounds'] = [File(1, filenames[0], user_upload_path + filenames[0], False), - File(2, filenames[1], user_upload_path + filenames[1], False)] - session.save() - - # Post description information - resp = self.client.post('/home/describe/sounds/', { - 'submit': ['Submit and continue'], - '0-lat': ['46.31658418182218'], - '0-lon': ['3.515625'], - '0-zoom': ['16'], - '0-tags': ['testtag1 testtag2 testtag3'], - '0-pack': [f'{existing_pack.id}'], - '0-license': ['3'], - '0-description': ['a test description for the sound file'], - '0-new_pack': [''], - '0-name': [filenames[0]], - '1-license': ['3'], - '1-description': ['another test description'], - '1-lat': [''], - '1-pack': [''], - '1-lon': [''], - '1-name': [filenames[1]], - '1-new_pack': ['Name of a new pack'], - '1-zoom': [''], - '1-tags': ['testtag1 testtag4 testtag5'], - }, follow=True) - - # Check that post redirected to first describe page with confirmation message on sounds described - self.assertRedirects(resp, '/home/describe/') - self.assertEqual('You have described all the selected files' in list(resp.context['messages'])[2].message, True) - - # Check that sounds have been created along with related tags, geotags and packs - self.assertEqual(user.sounds.all().count(), 2) - self.assertListEqual( - sorted(list(user.sounds.values_list('original_filename', flat=True))), - sorted([f for f in filenames])) - self.assertEqual(Pack.objects.filter(name='Name of a new pack').exists(), True) - self.assertEqual(Tag.objects.filter(name__contains="testtag").count(), 5) - self.assertNotEqual(user.sounds.get(original_filename=filenames[0]).geotag, None) - - @override_uploads_path_with_temp_directory - def test_describe_selected_files_bw(self): - # NOTE: because BW ui uses different views/templates for sound description, we write a complementary - # test for it # Create audio files filenames = ['file1.wav', 'filè2.wav'] @@ -196,9 +127,6 @@ def test_describe_selected_files_bw(self): File(2, filenames[1], user_upload_path + filenames[1], False)] session.save() - # Set BW frontend in session - test_using_bw_ui(self) - # Post description information resp = self.client.post('/home/describe/sounds/', { '0-lat': ['46.31658418182218'], @@ -277,7 +205,7 @@ def test_upload_csv(self, submit_job): # Test successful file upload and redirect filename = "file.csv" f = SimpleUploadedFile(filename, b"file_content") - resp = self.client.post(reverse('accounts-describe'), {'bulk-csv_file': f}) + resp = self.client.post(reverse('accounts-manage-sounds', args=['pending_description']), {'bulk-csv_file': f}) bulk = BulkUploadProgress.objects.get(user=user) self.assertRedirects(resp, reverse('accounts-bulk-describe', args=[bulk.id])) @@ -310,7 +238,7 @@ def test_bulk_describe_view_permissions(self): # Now user is not allowed to load the page as user.profile.can_do_bulk_upload() returns False self.client.force_login(user) resp = self.client.get(reverse('accounts-bulk-describe', args=[bulk.id]), follow=True) - self.assertRedirects(resp, reverse('accounts-home')) + self.assertRedirects(resp, reverse('accounts-manage-sounds', args=['pending_description'])) self.assertContains(resp, 'Your user does not have permission to use the bulk describe') @override_settings(BULK_UPLOAD_MIN_SOUNDS=0) @@ -334,7 +262,7 @@ def test_bulk_describe_state_finished_validation(self, submit_job): # Test that chosing option to delete existing BulkUploadProgress really does it resp = self.client.post(reverse('accounts-bulk-describe', args=[bulk.id]), data={'delete': True}) - self.assertRedirects(resp, reverse('accounts-describe')) # Redirects to describe page after delete + self.assertRedirects(resp, reverse('accounts-manage-sounds', args=['pending_description'])) # Redirects to describe page after delete self.assertEqual(BulkUploadProgress.objects.filter(user=user).count(), 0) # Test that chosing option to start describing files triggers bulk describe gearmnan job diff --git a/accounts/tests/test_user.py b/accounts/tests/test_user.py index d5f16311b..3fc73b531 100644 --- a/accounts/tests/test_user.py +++ b/accounts/tests/test_user.py @@ -116,7 +116,7 @@ def test_user_registration(self, magic_mock_function): 'email2': ['example@email.com'] }) self.assertEqual(resp.status_code, 200) - self.assertContains(resp, 'Registration done, activate your account') + self.assertContains(resp, 'feedbackRegistration=1') self.assertEqual(User.objects.filter(username=username).count(), 1) self.assertEqual(len(mail.outbox), 1) # An email was sent! self.assertTrue(settings.EMAIL_SUBJECT_PREFIX in mail.outbox[0].subject) @@ -769,30 +769,20 @@ def test_reset_view_with_email(self): """Check that the reset password view calls our form""" Site.objects.create(id=2, domain="freesound.org", name="Freesound") user = User.objects.create_user("testuser", email="testuser@freesound.org") - self.client.post(reverse("password_reset"), {"username_or_email": "testuser@freesound.org"}) + self.client.post(reverse("problems-logging-in"), {"username_or_email": "testuser@freesound.org"}) self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, "Password reset on Freesound") + self.assertEqual(mail.outbox[0].subject, "[freesound] Password reset") @override_settings(SITE_ID=2) def test_reset_view_with_username(self): """Check that the reset password view calls our form""" Site.objects.create(id=2, domain="freesound.org", name="Freesound") user = User.objects.create_user("testuser", email="testuser@freesound.org") - self.client.post(reverse("password_reset"), {"username_or_email": "testuser"}) + self.client.post(reverse("problems-logging-in"), {"username_or_email": "testuser"}) self.assertEqual(len(mail.outbox), 1) - self.assertEqual(mail.outbox[0].subject, "Password reset on Freesound") - - @override_settings(SITE_ID=2) - def test_reset_view_with_long_username(self): - """Check that the reset password fails with long username""" - Site.objects.create(id=2, domain="freesound.org", name="Freesound") - user = User.objects.create_user("testuser", email="testuser@freesound.org") - long_mail = ('1' * 255) + '@freesound.org' - resp = self.client.post(reverse("password_reset"), {"username_or_email": long_mail}) - - self.assertNotEqual(resp.context['form'].errors, None) + self.assertEqual(mail.outbox[0].subject, "[freesound] Password reset") class EmailResetTestCase(TestCase): @@ -844,16 +834,16 @@ def test_resend_activation_code_from_email(self): Check that resend activation code doesn't return an error with post request (use email to identify user) """ user = User.objects.create_user("testuser", email="testuser@freesound.org", is_active=False) - resp = self.client.post(reverse('accounts-resend-activation'), { - 'user': 'testuser@freesound.org', + resp = self.client.post(reverse('problems-logging-in'), { + 'username_or_email': 'testuser@freesound.org', }) self.assertEqual(resp.status_code, 200) self.assertEqual(len(mail.outbox), 1) # Check email was sent self.assertTrue(settings.EMAIL_SUBJECT_PREFIX in mail.outbox[0].subject) self.assertTrue(settings.EMAIL_SUBJECT_ACTIVATION_LINK in mail.outbox[0].subject) - resp = self.client.post(reverse('accounts-resend-activation'), { - 'user': 'new_email@freesound.org', + resp = self.client.post(reverse('problems-logging-in'), { + 'username_or_email': 'new_email@freesound.org', }) self.assertEqual(resp.status_code, 200) self.assertEqual(len(mail.outbox), 1) # Check no new email was sent (len() is same as before) @@ -863,60 +853,20 @@ def test_resend_activation_code_from_username(self): Check that resend activation code doesn't return an error with post request (use username to identify user) """ user = User.objects.create_user("testuser", email="testuser@freesound.org", is_active=False) - resp = self.client.post(reverse('accounts-resend-activation'), { - 'user': 'testuser', + resp = self.client.post(reverse('problems-logging-in'), { + 'username_or_email': 'testuser', }) self.assertEqual(resp.status_code, 200) self.assertEqual(len(mail.outbox), 1) # Check email was sent self.assertTrue(settings.EMAIL_SUBJECT_PREFIX in mail.outbox[0].subject) self.assertTrue(settings.EMAIL_SUBJECT_ACTIVATION_LINK in mail.outbox[0].subject) - resp = self.client.post(reverse('accounts-resend-activation'), { - 'user': 'testuser_does_not_exist', + resp = self.client.post(reverse('problems-logging-in'), { + 'username_or_email': 'testuser_does_not_exist', }) self.assertEqual(resp.status_code, 200) self.assertEqual(len(mail.outbox), 1) # Check no new email was sent (len() is same as before) - def test_resend_activation_code_from_long_username(self): - """ - Check that resend activation code returns an error if username is too long - """ - long_mail = ('1' * 255) + '@freesound.org' - resp = self.client.post(reverse('accounts-resend-activation'), { - 'user': long_mail, - }) - self.assertNotEqual(resp.context['form'].errors, None) - self.assertEqual(len(mail.outbox), 0) # Check email wasn't sent - - -class UsernameReminderTestCase(TestCase): - def test_username_reminder(self): - """ Check that send username reminder doesn't return an error with post request """ - user = User.objects.create_user("testuser", email="testuser@freesound.org") - resp = self.client.post(reverse('accounts-username-reminder'), { - 'user': 'testuser@freesound.org', - }) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(mail.outbox), 1) # Check email was sent - self.assertTrue(settings.EMAIL_SUBJECT_PREFIX in mail.outbox[0].subject) - self.assertTrue(settings.EMAIL_SUBJECT_USERNAME_REMINDER in mail.outbox[0].subject) - - resp = self.client.post(reverse('accounts-username-reminder'), { - 'user': 'new_email@freesound.org', - }) - self.assertEqual(resp.status_code, 200) - self.assertEqual(len(mail.outbox), 1) # Check no new email was sent (len() is same as before) - - def test_username_reminder_length(self): - """ Check that send long username reminder return an error with post request """ - long_mail = ('1' * 255) + '@freesound.org' - user = User.objects.create_user("testuser", email="testuser@freesound.org") - resp = self.client.post(reverse('accounts-username-reminder'), { - 'user': long_mail, - }) - self.assertNotEqual(resp.context['form'].errors, None) - self.assertEqual(len(mail.outbox), 0) - class ChangeUsernameTest(TestCase): @@ -958,14 +908,14 @@ def test_change_username_form_profile_page(self): userA = User.objects.create_user('userA', email='userA@freesound.org', password='testpass') self.client.login(username='userA', password='testpass') - # Test save profile without changing username - resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userA']}) - self.assertRedirects(resp, reverse('accounts-home')) # Successful edit redirects to home + # Test save profile without changing username (note we set all mandatory fields) + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userA'], 'profile-ui_theme_preference': 'f'}) + self.assertRedirects(resp, reverse('accounts-edit')) self.assertEqual(OldUsername.objects.filter(user=userA).count(), 0) # Try rename user with an existing username from another user userB = User.objects.create_user('userB', email='userB@freesound.org') - resp = self.client.post(reverse('accounts-edit'), data={'profile-username': [userB.username]}) + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': [userB.username], 'profile-ui_theme_preference': 'f'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.context['profile_form'].has_error('username'), True) # Error in username field self.assertIn('This username is already taken or has been in used in the past', @@ -975,14 +925,14 @@ def test_change_username_form_profile_page(self): self.assertEqual(OldUsername.objects.filter(user=userA).count(), 0) # Now rename user for the first time - resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userANewName']}) - self.assertRedirects(resp, reverse('accounts-home')) # Successful edit redirects to home + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userANewName'], 'profile-ui_theme_preference': 'f'}) + self.assertRedirects(resp, reverse('accounts-edit')) userA.refresh_from_db() self.assertEqual(userA.username, 'userANewName') self.assertEqual(OldUsername.objects.filter(user=userA).count(), 1) # Try rename again user with a username that was already used by the same user in the past - resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userA']}) + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userA'], 'profile-ui_theme_preference': 'f'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.context['profile_form'].has_error('username'), True) # Error in username field self.assertIn('This username is already taken or has been in used in the past', @@ -992,8 +942,8 @@ def test_change_username_form_profile_page(self): self.assertEqual(OldUsername.objects.filter(user=userA).count(), 1) # Now rename user for the second time - resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userANewNewName']}) - self.assertRedirects(resp, reverse('accounts-home')) # Successful edit redirects to home + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userANewNewName'], 'profile-ui_theme_preference': 'f'}) + self.assertRedirects(resp, reverse('accounts-edit')) userA.refresh_from_db() self.assertEqual(userA.username, 'userANewNewName') self.assertEqual(OldUsername.objects.filter(user=userA).count(), 2) @@ -1003,8 +953,8 @@ def test_change_username_form_profile_page(self): # NOTE: when USERNAME_CHANGE_MAX_TIMES is reached, the form renders the "username" field as "disabled" and # therefore the username can't be changed. Other than that the form behaves normally, therefore no # form errors will be raised because the field is ignored - resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userANewNewNewName']}) - self.assertRedirects(resp, reverse('accounts-home')) # Successful edit redirects to home but... + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userANewNewNewName'], 'profile-ui_theme_preference': 'f'}) + self.assertRedirects(resp, reverse('accounts-edit')) # Successful edit redirects to home but... userA.refresh_from_db() self.assertEqual(userA.username, 'userANewNewName') # ...username has not changed... self.assertEqual(OldUsername.objects.filter(user=userA).count(), 2) # ...and no new OldUsername objects created @@ -1093,8 +1043,8 @@ def test_change_username_case_insensitiveness(self): self.client.login(username='userA', password='testpass') # Rename "userA" to "UserA", should not create OldUsername object - resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['UserA']}) - self.assertRedirects(resp, reverse('accounts-home')) + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['UserA'], 'profile-ui_theme_preference': 'f'}) + self.assertRedirects(resp, reverse('accounts-edit')) userA.refresh_from_db() self.assertEqual(userA.username, 'UserA') # Username capitalization was changed ... self.assertEqual(OldUsername.objects.filter(user=userA).count(), 0) # ... but not OldUsername was created diff --git a/accounts/tests/test_views.py b/accounts/tests/test_views.py index eb2bb4a70..ec7b79507 100644 --- a/accounts/tests/test_views.py +++ b/accounts/tests/test_views.py @@ -23,15 +23,17 @@ from django.contrib.auth.models import User, Permission from django.test import TestCase from django.urls import reverse +from unittest import mock from accounts.models import OldUsername +from geotags.models import GeoTag from sounds.models import SoundOfTheDay, Download, PackDownload -from utils.test_helpers import create_user_and_sounds, test_using_bw_ui +from utils.test_helpers import create_user_and_sounds, create_fake_perform_search_engine_query_results_tags_mode class SimpleUserTest(TestCase): - fixtures = ['licenses'] + fixtures = ['licenses', 'users', 'follow'] def setUp(self): user, packs, sounds = create_user_and_sounds(num_packs=1, num_sounds=5) @@ -44,22 +46,100 @@ def setUp(self): self.sound.moderation_state = "OK" self.sound.processing_state = "OK" self.sound.similarity_state = "OK" + self.sound.geotag = GeoTag.objects.create(user=user, lat=45.8498, lon=-62.6879, zoom=9) self.sound.save() SoundOfTheDay.objects.create(sound=self.sound, date_display=datetime.date.today()) self.download = Download.objects.create(user=self.user, sound=self.sound, license=self.sound.license, created=self.sound.created) self.pack_download = PackDownload.objects.create(user=self.user, pack=self.pack, created=self.pack.created) + def test_old_ng_redirects(self): + # Test that some pages which used to have its own "URL" in NG now redirect to other pages and open as a modal + + # Comments on user sounds + resp = self.client.get(reverse('comments-for-user', kwargs={'username': self.user.username})) + self.assertRedirects(resp, reverse('account', args=[self.user.username]) + '?comments=1') + + # Comments from a user + resp = self.client.get(reverse('comments-by-user', kwargs={'username': self.user.username})) + self.assertRedirects(resp, reverse('account', args=[self.user.username]) + '?comments_by=1') + + # User downloaded soudns + resp = self.client.get(reverse('user-downloaded-sounds', kwargs={'username': self.user.username})) + self.assertRedirects(resp, reverse('account', args=[self.user.username]) + '?downloaded_sounds=1') + + # User downloaded packs + resp = self.client.get(reverse('user-downloaded-packs', kwargs={'username': self.user.username})) + self.assertRedirects(resp, reverse('account', args=[self.user.username]) + '?downloaded_packs=1') + + # Users that downloaded a sound + resp = self.client.get( + reverse('sound-downloaders', kwargs={'username': self.user.username, "sound_id": self.sound.id})) + self.assertRedirects(resp, reverse('sound', args=[self.user.username, self.sound.id]) + '?downloaders=1') + + # Users that downloaded a pack + resp = self.client.get( + reverse('pack-downloaders', kwargs={'username': self.user.username, "pack_id": self.pack.id})) + self.assertRedirects(resp, reverse('pack', args=[self.user.username, self.pack.id]) + '?downloaders=1') + + # Users following user + resp = self.client.get(reverse('user-followers', args=['User2'])) + self.assertRedirects(resp, reverse('account', args=['User2']) + '?followers=1') + + # Users followed by user + resp = self.client.get(reverse('user-following-users', args=['User2'])) + self.assertRedirects(resp, reverse('account', args=['User2']) + '?following=1') + + # Tags followed by user + resp = self.client.get(reverse('user-following-tags', args=['User2'])) + self.assertRedirects(resp, reverse('account', args=['User2']) + '?followingTags=1') + + # Sound tags followed by user + resp = self.client.get(reverse('user-following-tags', args=['User2'])) + self.assertRedirects(resp, reverse('account', args=['User2']) + '?followingTags=1') + + # Similar sounds + resp = self.client.get(reverse('sound-similar', args=[self.user.username, self.sound.id])) + self.assertRedirects(resp, reverse('sound', args=[self.user.username, self.sound.id]) + '?similar=1') + + # Sound remix group + resp = self.client.get(reverse('sound-remixes', args=[self.user.username, self.sound.id])) + self.assertRedirects(resp, reverse('sound', args=[self.user.username, self.sound.id]) + '?remixes=1') + + # Packs for user + resp = self.client.get(reverse('packs-for-user', kwargs={'username': self.user.username})) + self.assertEqual(resp.status_code, 302) + self.assertTrue(reverse('sounds-search') in resp.url and self.user.username in resp.url) + + # Sounds for user + resp = self.client.get(reverse('sounds-for-user', kwargs={'username': self.user.username})) + self.assertEqual(resp.status_code, 302) + self.assertTrue(reverse('sounds-search') in resp.url and self.user.username in resp.url) + + self.client.force_login(self.user) + + # Sound edit page + resp = self.client.get( + reverse('sound-edit-sources', args=[self.user.username, self.sound.id])) + self.assertRedirects(resp, reverse('sound-edit', args=[self.user.username, self.sound.id])) + + # Flag sound + resp = self.client.get(reverse('sound-flag', args=[self.user.username, self.sound.id])) + self.assertRedirects(resp, reverse('sound', args=[self.user.username, self.sound.id]) + '?flag=1') + + # Home to account + resp = self.client.get(reverse('accounts-home')) + self.assertRedirects(resp, reverse('account', args=[self.user.username])) + + # Describe to manage sounds page + resp = self.client.get(reverse('accounts-describe')) + self.assertRedirects(resp, reverse('accounts-manage-sounds', args=['pending_description'])) + def test_account_response(self): # 200 response on account access resp = self.client.get(reverse('account', kwargs={'username': self.user.username})) self.assertEqual(resp.status_code, 200) - def test_user_sounds_response(self): - # 200 response on user sounds access - resp = self.client.get(reverse('sounds-for-user', kwargs={'username': self.user.username})) - self.assertEqual(resp.status_code, 200) - def test_user_flag_response(self): # 200 response on user flag and clear flag access self.user.set_password('12345') @@ -68,14 +148,14 @@ def test_user_flag_response(self): self.client.force_login(self.user) resp = self.client.get(reverse('flag-user', kwargs={'username': self.user.username})) self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('clear-flags-user', kwargs={'username': self.user.username})) - self.assertEqual(resp.status_code, 200) + resp = self.client.get(reverse('clear-flags-user', kwargs={'username': self.user.username}), follow=True) + self.assertContains(resp, f'0 flags cleared for user {self.user.username}') def test_user_comments_response(self): - # 200 response on user comments and comments for user access - resp = self.client.get(reverse('comments-for-user', kwargs={'username': self.user.username})) + # 200 response on user comments and comments for user modal + resp = self.client.get(reverse('comments-for-user', kwargs={'username': self.user.username}) + '?ajax=1') self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('comments-by-user', kwargs={'username': self.user.username})) + resp = self.client.get(reverse('comments-by-user', kwargs={'username': self.user.username}) + '?ajax=1') self.assertEqual(resp.status_code, 200) # If user is deleted, get 404 @@ -95,16 +175,11 @@ def test_user_geotags_response(self): resp = self.client.get(reverse('geotags-for-user', kwargs={'username': self.user.username})) self.assertEqual(resp.status_code, 404) - def test_user_packs_response(self): - # 200 response on user packs access - resp = self.client.get(reverse('packs-for-user', kwargs={'username': self.user.username})) - self.assertEqual(resp.status_code, 200) - def test_user_downloaded_response(self): - # 200 response on user downloaded sounds and packs access - resp = self.client.get(reverse('user-downloaded-sounds', kwargs={'username': self.user.username})) + # 200 response on user downloaded sounds and packs modals + resp = self.client.get(reverse('user-downloaded-sounds', kwargs={'username': self.user.username}) + '?ajax=1') self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('user-downloaded-packs', kwargs={'username': self.user.username})) + resp = self.client.get(reverse('user-downloaded-packs', kwargs={'username': self.user.username}) + '?ajax=1') self.assertEqual(resp.status_code, 200) # If user is deleted, get 404 @@ -115,12 +190,12 @@ def test_user_downloaded_response(self): self.assertEqual(resp.status_code, 404) def test_user_follow_response(self): - # 200 response on user user bookmarks sounds and packs access - resp = self.client.get(reverse('user-following-users', kwargs={'username': self.user.username})) + # 200 response on user following modals + resp = self.client.get(reverse('user-following-users', kwargs={'username': self.user.username}) + '?ajax=1') self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('user-followers', kwargs={'username': self.user.username})) + resp = self.client.get(reverse('user-followers', kwargs={'username': self.user.username}) + '?ajax=1') self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('user-following-tags', kwargs={'username': self.user.username})) + resp = self.client.get(reverse('user-following-tags', kwargs={'username': self.user.username}) + '?ajax=1') self.assertEqual(resp.status_code, 200) def test_download_attribution_csv(self): @@ -156,53 +231,55 @@ def test_download_attribution_txt(self): self.assertEqual(resp.status_code, 404) def test_sounds_response(self): - # 200 response on sounds page access + # 302 response on sounds page access (since BW, there is a redicrect to the search page) resp = self.client.get(reverse('sounds')) - self.assertEqual(resp.status_code, 200) - + self.assertEqual(resp.status_code, 302) + self.assertTrue(reverse('sounds-search') in resp.url) + + # Test other sound related views. Nota that since BW many of these will include redirects user = self.sound.user user.set_password('12345') user.is_superuser = True user.save() self.client.force_login(user) + resp = self.client.get(reverse('sound', kwargs={'username': user.username, "sound_id": self.sound.id})) self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('sound-flag', kwargs={'username': user.username, "sound_id": self.sound.id})) - self.assertEqual(resp.status_code, 200) - resp = self.client.get( - reverse('sound-edit-sources', kwargs={'username': user.username, "sound_id": self.sound.id})) + + resp = self.client.get(reverse('sound-flag', kwargs={'username': user.username, "sound_id": self.sound.id}) + '?ajax=1') self.assertEqual(resp.status_code, 200) + resp = self.client.get(reverse('sound-edit', kwargs={'username': user.username, "sound_id": self.sound.id})) self.assertEqual(resp.status_code, 200) + resp = self.client.get(reverse('sound-geotag', kwargs={'username': user.username, "sound_id": self.sound.id})) self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('sound-similar', kwargs={'username': user.username, "sound_id": self.sound.id})) + + resp = self.client.get(reverse('sound-similar', kwargs={'username': user.username, "sound_id": self.sound.id}) + '?ajax=1') self.assertEqual(resp.status_code, 200) - resp = self.client.get(reverse('sound-delete', kwargs={'username': user.username, "sound_id": self.sound.id})) + + resp = self.client.get( + reverse('sound-downloaders', kwargs={'username': user.username, "sound_id": self.sound.id}) + '?ajax=1') self.assertEqual(resp.status_code, 200) + resp = self.client.get( - reverse('sound-downloaders', kwargs={'username': user.username, "sound_id": self.sound.id})) + reverse('pack-downloaders', kwargs={'username': user.username, "pack_id": self.pack.id}) + '?ajax=1') self.assertEqual(resp.status_code, 200) - def test_tags_response(self): + @mock.patch('search.views.perform_search_engine_query') + def test_tags_response(self, perform_search_engine_query): + perform_search_engine_query.return_value = (create_fake_perform_search_engine_query_results_tags_mode(), None) + # 200 response on tags page access resp = self.client.get(reverse('tags')) self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.context['tags_mode'], True) def test_packs_response(self): - # 200 response on packs page access + # 302 response (note that since BW, there will be a redirect to the search page in between) resp = self.client.get(reverse('packs')) - self.assertEqual(resp.status_code, 200) - - def test_comments_response(self): - # 200 response on comments page access - resp = self.client.get(reverse('comments')) - self.assertEqual(resp.status_code, 200) - - def test_remixed_response(self): - # 200 response on remixed sounds page access - resp = self.client.get(reverse('remix-groups')) - self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.status_code, 302) + self.assertTrue(reverse('sounds-search') in resp.url) def test_contact_response(self): # 200 response on contact page access @@ -225,17 +302,17 @@ def test_geotags_box_iframe_response(self): self.assertEqual(resp.status_code, 200) def test_accounts_manage_pages(self): - # 200 response on Account registration page + # In BW account registration loads as a modal resp = self.client.get(reverse('accounts-register')) self.assertEqual(resp.status_code, 200) - # 200 response on Account reactivation page + # In BW Account resend activations redirects to "problems logging in" in front page resp = self.client.get(reverse('accounts-resend-activation')) - self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.status_code, 302) - # 200 response on Account username reminder page + # In BW Account resend activations redirects to "problems logging in" in front page resp = self.client.get(reverse('accounts-username-reminder')) - self.assertEqual(resp.status_code, 200) + self.assertEqual(resp.status_code, 302) # Login user with moderation permissions user = User.objects.create_user("anothertestuser") @@ -252,10 +329,11 @@ def test_accounts_manage_pages(self): resp = self.client.get(reverse('accounts-email-reset')) self.assertEqual(resp.status_code, 200) - # 200 response on Account home page + # In BW, home page does not really exist resp = self.client.get(reverse('accounts-home')) - self.assertEqual(resp.status_code, 200) - + self.assertEqual(resp.status_code, 302) + self.assertEqual(resp.url, reverse('account', args=[user.username])) + # 200 response on Account edit page resp = self.client.get(reverse('accounts-edit')) self.assertEqual(resp.status_code, 200) @@ -363,9 +441,6 @@ def test_username_check(self): def test_accounts_manage_sounds_pages(self): self.client.force_login(self.user) - # Set BW frontend in session (as these pages are BW only) - test_using_bw_ui(self) - # 200 response on manage sounds page - published resp = self.client.get(reverse('accounts-manage-sounds', args=['published'])) self.assertEqual(resp.status_code, 200) diff --git a/accounts/views.py b/accounts/views.py index 3f49656c6..1af794620 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -122,8 +122,10 @@ def login(request, template_name, authentication_form): # NOTE: in the function below we need to make the template depend on the front-end because LoginView will not # use our custom "render" function which would select the template for the chosen front-end automatically # Also, we set the authentication form depending on front-end as there are small modifications. + if using_beastwhoosh(request) and template_name == 'registration/login.html': + template_name = 'accounts/login.html' response = LoginView.as_view( - template_name=template_name if not using_beastwhoosh(request) else 'accounts/login.html', + template_name=template_name, authentication_form=authentication_form if not using_beastwhoosh(request) else BwFsAuthenticationForm)(request) if isinstance(response, HttpResponseRedirect): # If there is a redirect it's because the login was successful @@ -375,7 +377,7 @@ def send_activation(user): 'username': username, 'hash': token } - send_mail_template(settings.EMAIL_SUBJECT_ACTIVATION_LINK, 'accounts/email_activation.txt', tvars, user_to=user) + send_mail_template(settings.EMAIL_SUBJECT_ACTIVATION_LINK, 'emails/email_activation.txt', tvars, user_to=user) @redirect_if_beastwhoosh('front-page', query_string='loginProblems=1') @@ -407,7 +409,7 @@ def username_reminder(request): try: user = User.objects.get(email__iexact=email) - send_mail_template(settings.EMAIL_SUBJECT_USERNAME_REMINDER, 'accounts/email_username_reminder.txt', + send_mail_template(settings.EMAIL_SUBJECT_USERNAME_REMINDER, 'emails/email_username_reminder.txt', {'user': user}, user_to=user) except User.DoesNotExist: pass @@ -689,9 +691,8 @@ def process_filter_and_sort_options(request, sort_options, tab): except ValueError: pack_ids = [] packs = Pack.objects.ordered_ids(pack_ids) - if not request.user.is_superuser: - # Unless user is superuser, only allow to select packs owned by user - packs = [pack for pack in packs if pack.user == pack.user] + # Just as a sanity check, filter out packs not owned by the user + packs = [pack for pack in packs if pack.user == pack.user] if packs: if 'delete_confirm' in request.POST: # Delete the selected packs @@ -734,9 +735,8 @@ def process_filter_and_sort_options(request, sort_options, tab): except ValueError: sound_ids = [] sounds = Sound.objects.ordered_ids(sound_ids) - if not request.user.is_superuser: - # Unless user is superuser, only allow to select sounds owned by user - sounds = [sound for sound in sounds if sound.user == request.user] + # Just as a sanity check, filter out sounds not owned by the user + sounds = [sound for sound in sounds if sound.user == request.user] if sounds: if 'edit' in request.POST: # Edit the selected sounds @@ -871,8 +871,10 @@ def sounds_pending_description_helper(request, file_structure, files): user_uploads_dir = request.user.profile.locations()['uploads_dir'] remove_directory_if_empty(user_uploads_dir) remove_empty_user_directory_from_mirror_locations(user_uploads_dir) - - return HttpResponseRedirect(reverse('accounts-describe')) + if using_beastwhoosh(request): + return HttpResponseRedirect(reverse('accounts-manage-sounds', args=['pending_description'])) + else: + return HttpResponseRedirect(reverse('accounts-describe')) elif "describe" in request.POST: clear_session_edit_and_describe_data(request) request.session['describe_sounds'] = [files[x] for x in form.cleaned_data["files"]] @@ -1726,7 +1728,7 @@ def email_reset(request): 'token': default_token_generator.make_token(user) } send_mail_template(settings.EMAIL_SUBJECT_EMAIL_CHANGED, - 'accounts/email_reset_email.txt', tvars, + 'emails/email_reset_email.txt', tvars, email_to=email) return HttpResponseRedirect(reverse('accounts-email-reset-done')) @@ -1786,7 +1788,7 @@ def email_reset_complete(request, uidb36=None, token=None): 'activePage': 'email' # For BW account settings sidebar } send_mail_template(settings.EMAIL_SUBJECT_EMAIL_CHANGED, - 'accounts/email_reset_complete_old_address_notification.txt', tvars, email_to=old_email) + 'emails/email_reset_complete_old_address_notification.txt', tvars, email_to=old_email) return render(request, 'accounts/email_reset_complete.html', tvars) @@ -1815,8 +1817,8 @@ def problems_logging_in(request): pwd_reset_form = FsPasswordResetForm(request.POST) if pwd_reset_form.is_valid(): pwd_reset_form.save( - subject_template_name='registration/password_reset_subject.txt', - email_template_name='registration/password_reset_email.html', + subject_template_name='emails/password_reset_subject.txt', + email_template_name='emails/password_reset_email.html', use_https=request.is_secure(), request=request ) @@ -1890,9 +1892,9 @@ def flag_user(request, username): clear_url = reverse("clear-flags-user", args=[flagged_user.username]) clear_url = request.build_absolute_uri(clear_url) if reports_count < settings.USERFLAG_THRESHOLD_FOR_AUTOMATIC_BLOCKING: - template_to_use = 'accounts/report_spammer_admins.txt' + template_to_use = 'emails/email_report_spammer_admins.txt' else: - template_to_use = 'accounts/report_blocked_spammer_admins.txt' + template_to_use = 'emails/email_report_blocked_spammer_admins.txt' tvars = {'flagged_user': flagged_user, 'objects_data': objects_data, @@ -1913,7 +1915,11 @@ def clear_flags_user(request, username): for flag in flags: flag.delete() tvars = {'num': num, 'username': username} - return render(request, 'accounts/flags_cleared.html', tvars) + if using_beastwhoosh(request): + messages.add_message(request, messages.INFO, f'{num} flag{"" if num == 1 else "s"} cleared for user {username}') + return HttpResponseRedirect(reverse('account', args=[username])) + else: + return render(request, 'accounts/flags_cleared.html', tvars) else: return HttpResponseRedirect(reverse('login')) diff --git a/apiv2/urls.py b/apiv2/urls.py index 8551411ac..b7ace292f 100644 --- a/apiv2/urls.py +++ b/apiv2/urls.py @@ -97,8 +97,8 @@ # Oauth2 path('oauth2/', include('apiv2.oauth2_urls', namespace='oauth2_provider')), - path('login/', login, {'template_name': 'api/minimal_login.html', - 'authentication_form': FsAuthenticationForm}, name="api-login"), + path('login/', login, {'template_name': 'oauth2_provider/oauth_login.html', + 'authentication_form': FsAuthenticationForm}, name="api-login"), path('logout/', LogoutView.as_view(next_page='/apiv2/'), name="api-logout"), ######### diff --git a/bookmarks/tests.py b/bookmarks/tests.py index 7812c5271..93b8fbe9f 100644 --- a/bookmarks/tests.py +++ b/bookmarks/tests.py @@ -103,19 +103,19 @@ def test_others_bookmarks(self): self.assertEqual(200, response.status_code) self.assertContains(response, 'Bookmarks by Anton') + @override_settings(BW_BOOKMARK_PAGES_PUBLIC=True) def test_no_bookmarks(self): user = User.objects.get(username='Anton') - self.client.force_login(user) response = self.client.get(reverse('bookmarks-for-user', kwargs={'username': user.username})) self.assertEqual(200, response.status_code) - self.assertContains(response, 'Your bookmarks') + self.assertContains(response, 'Bookmarks') self.assertContains(response, 'There are no uncategorized bookmarks') + @override_settings(BW_BOOKMARK_PAGES_PUBLIC=True) def test_bookmark_category(self): user = User.objects.get(username='Anton') - self.client.force_login(user) category = bookmarks.models.BookmarkCategory.objects.create(name='Category1', user=user) bookmarks.models.Bookmark.objects.create(user=user, sound_id=10) @@ -127,11 +127,10 @@ def test_bookmark_category(self): self.assertEqual(200, response.status_code) self.assertEqual(2, len(response.context['page'].object_list)) - self.assertContains(response, 'Your bookmarks') - self.assertContains(response, 'Bookmarks in "Category1"') - self.assertContains(response, 'Uncategorized (1 bookmark)') - self.assertContains(response, 'Category1 (2 bookmarks)') + self.assertContains(response, 'Bookmarks') + self.assertContains(response, 'Category1') + @override_settings(BW_BOOKMARK_PAGES_PUBLIC=True) def test_bookmark_category_oldusername(self): user = User.objects.get(username='Anton') self.client.force_login(user) @@ -156,7 +155,5 @@ def test_bookmark_category_oldusername(self): self.assertEqual(200, response.status_code) self.assertEqual(2, len(response.context['page'].object_list)) - self.assertContains(response, 'Your bookmarks') - self.assertContains(response, 'Bookmarks in "Category1"') - self.assertContains(response, 'Uncategorized (1 bookmark)') - self.assertContains(response, 'Category1 (2 bookmarks)') + self.assertContains(response, 'Bookmarks') + self.assertContains(response, 'Category1') diff --git a/donations/management/commands/send_donation_request_emails.py b/donations/management/commands/send_donation_request_emails.py index 8b36c3042..078a1e5a7 100644 --- a/donations/management/commands/send_donation_request_emails.py +++ b/donations/management/commands/send_donation_request_emails.py @@ -81,7 +81,7 @@ def handle(self, **options): for user in users_to_notify.all(): email_sent_successfully = send_mail_template( settings.EMAIL_SUBJECT_DONATION_REMINDER, - 'donations/email_donation_reminder.txt', {'user': user}, + 'emails/email_donation_reminder.txt', {'user': user}, user_to=user, email_type_preference_check='donation_request') if email_sent_successfully: user.profile.last_donation_email_sent = datetime.datetime.now() @@ -145,7 +145,7 @@ def handle(self, **options): if send_email: email_sent_successfully = send_mail_template( settings.EMAIL_SUBJECT_DONATION_REQUEST, - 'donations/email_donation_request.txt', { + 'emails/email_donation_request.txt', { 'user': user, }, user_to=user, email_type_preference_check='donation_request') diff --git a/donations/views.py b/donations/views.py index 3930e1ea2..5ee304bec 100644 --- a/donations/views.py +++ b/donations/views.py @@ -60,7 +60,7 @@ def _save_donation(encoded_data, email, amount, currency, transaction_id, source email_to = None if user is not None else email send_mail_template( settings.EMAIL_SUBJECT_DONATION_THANK_YOU, - 'donations/email_donation.txt', { + 'emails/email_donation.txt', { 'user': user, 'amount': amount, 'display_name': display_name diff --git a/follow/management/commands/send_stream_emails.py b/follow/management/commands/send_stream_emails.py index 36f1fd385..b7f7f5732 100644 --- a/follow/management/commands/send_stream_emails.py +++ b/follow/management/commands/send_stream_emails.py @@ -95,7 +95,7 @@ def handle(self, *args, **options): tvars = {'username': username, 'users_sounds': users_sounds, 'tags_sounds': tags_sounds} - text_content = render_mail_template('follow/email_stream.txt', tvars) + text_content = render_mail_template('emails/email_stream.txt', tvars) # Send email try: diff --git a/follow/tests.py b/follow/tests.py index 4a45d45ab..dbd3870d0 100644 --- a/follow/tests.py +++ b/follow/tests.py @@ -17,8 +17,10 @@ # Authors: # See AUTHORS file. # -from django.test import TestCase + from django.contrib.auth.models import User +from django.test import TestCase +from django.urls import reverse from follow.models import FollowingUserItem, FollowingQueryItem @@ -32,11 +34,11 @@ def setUp(self): def test_following_users(self): # If we get following users for someone who exists, OK - resp = self.client.get("/people/User2/following_users/") + resp = self.client.get(reverse('user-following-users', args=['User2']) + '?ajax=1') self.assertEqual(resp.status_code, 200) # Someone who doesn't exist should give 404 - resp = self.client.get("/people/nouser/following_users/") + resp = self.client.get(reverse('user-following-users', args=['User32']) + '?ajax=1') self.assertEqual(resp.status_code, 404) def test_following_users_oldusername(self): @@ -44,16 +46,17 @@ def test_following_users_oldusername(self): user.username = "new-username" user.save() # If we get following users for someone who exists by it's old username - resp = self.client.get("/people/User2/following_users/") + resp = self.client.get(reverse('user-following-users', args=['User2']) + '?ajax=1') self.assertEqual(resp.status_code, 301) - - def test_followers(self): + + def test_followers_modal(self): # If we get following users for someone who exists, OK - resp = self.client.get("/people/User2/followers/") + resp = self.client.get(reverse('user-followers', args=['User2']) + '?ajax=1') self.assertEqual(resp.status_code, 200) + self.assertContains(resp, "User2's followers") # Someone who doesn't exist should give 404 - resp = self.client.get("/people/nouser/followers/") + resp = self.client.get(reverse('user-followers', args=['User32']) + '?ajax=1') self.assertEqual(resp.status_code, 404) def test_followers_oldusername(self): @@ -61,16 +64,16 @@ def test_followers_oldusername(self): user.username = "new-username" user.save() # If we get following users for someone who exists by it's old username - resp = self.client.get("/people/User2/followers/") + resp = self.client.get(reverse('user-followers', args=['User2']) + '?ajax=1') self.assertEqual(resp.status_code, 301) def test_following_tags(self): # If we get following tags for someone who exists, OK - resp = self.client.get("/people/User2/following_tags/") + resp = self.client.get(reverse('user-following-tags', args=['User2']) + '?ajax=1') self.assertEqual(resp.status_code, 200) # Someone who doesn't exist should give 404 - resp = self.client.get("/people/nouser/following_tags/") + resp = self.client.get(reverse('user-following-tags', args=['User32']) + '?ajax=1') self.assertEqual(resp.status_code, 404) def test_following_tags_oldusename(self): @@ -78,20 +81,20 @@ def test_following_tags_oldusename(self): user.username = "new-username" user.save() # If we get following tags for someone who exists by it's old username - resp = self.client.get("/people/User2/following_tags/") + resp = self.client.get(reverse('user-following-tags', args=['User2']) + '?ajax=1') self.assertEqual(resp.status_code, 301) def test_follow_user(self): # Start following unexisting user - resp = self.client.get("/follow/follow_user/nouser/") + resp = self.client.get(reverse('follow-user', args=['nouser'])) self.assertEqual(resp.status_code, 404) # Start following existing user - resp = self.client.get("/follow/follow_user/User1/") + resp = self.client.get(reverse('follow-user', args=['User1'])) self.assertEqual(resp.status_code, 200) # Start following user you already follow - resp = self.client.get("/follow/follow_user/User1/") + resp = self.client.get(reverse('follow-user', args=['User1'])) self.assertEqual(resp.status_code, 200) # Check that user is actually following the other user @@ -99,15 +102,15 @@ def test_follow_user(self): FollowingUserItem.objects.filter(user_from__username='testuser', user_to__username='User1').exists(), True) # Stop following unexisting user - resp = self.client.get("/follow/unfollow_user/nouser/") + resp = self.client.get(reverse('unfollow-user', args=['nouser'])) self.assertEqual(resp.status_code, 404) # Stop following user you are not actually following - resp = self.client.get("/follow/unfollow_user/User3/") + resp = self.client.get(reverse('unfollow-user', args=['User1'])) self.assertEqual(resp.status_code, 200) # Stop following user you follow - resp = self.client.get("/follow/unfollow_user/User1/") + resp = self.client.get(reverse('unfollow-user', args=['User1'])) self.assertEqual(resp.status_code, 200) # Check that user is no longer following the other user @@ -116,11 +119,11 @@ def test_follow_user(self): def test_follow_tags(self): # Start following group of tags - resp = self.client.get("/follow/follow_tags/field-recording/another_tag/") + resp = self.client.get(reverse('follow-tags', args=['field-recording/another_tag'])) self.assertEqual(resp.status_code, 200) # Start following group of tags you already follow - resp = self.client.get("/follow/follow_tags/field-recording/another_tag/") + resp = self.client.get(reverse('follow-tags', args=['field-recording/another_tag'])) self.assertEqual(resp.status_code, 200) # Check that user is actually following the tags @@ -128,11 +131,11 @@ def test_follow_tags(self): FollowingQueryItem.objects.filter(user__username='testuser', query='field-recording another_tag').exists(), True) # Stop following group of tags you do not already follow - resp = self.client.get("/follow/unfollow_tags/a-tag/another_tag/") + resp = self.client.get(reverse('unfollow-tags', args=['a-tag/another_tag'])) self.assertEqual(resp.status_code, 200) # Stop following group of tags you already follow - resp = self.client.get("/follow/unfollow_tags/field-recording/another_tag/") + resp = self.client.get(reverse('unfollow-tags', args=['field-recording/another_tag'])) self.assertEqual(resp.status_code, 200) # Check that user is no longer following the tags @@ -141,5 +144,5 @@ def test_follow_tags(self): def test_stream(self): # Stream should return OK - resp = self.client.get("/home/stream/") + resp = self.client.get(reverse('stream')) self.assertEqual(resp.status_code, 200) diff --git a/forum/tests.py b/forum/tests.py index 89a324426..f4fe17be4 100644 --- a/forum/tests.py +++ b/forum/tests.py @@ -448,26 +448,6 @@ def test_edit_post_response_ok(self): edited_post = Post.objects.get(id=post.id) self.assertEqual(edited_post.body, 'Edited post body') - def test_delete_post_response_ok(self): - forum = Forum.objects.first() - thread = forum.thread_set.first() - post = thread.post_set.first() - - # Assert non-logged in user can't delete post - resp = self.client.get(reverse('forums-post-delete', args=[post.id])) - self.assertRedirects(resp, f"{reverse('login')}?next={reverse('forums-post-delete', args=[post.id])}") - - # Assert logged in user which is not author of post can't delete post - user2 = User.objects.create_user(username='testuser2', email='email2@example.com', password='12345') - self.client.force_login(user2) - resp = self.client.get(reverse('forums-post-delete', args=[post.id])) - self.assertEqual(resp.status_code, 404) - - # Assert logged in user can delete post (see delete confirmation page) - self.client.force_login(self.user) - resp = self.client.get(reverse('forums-post-delete', args=[post.id])) - self.assertEqual(resp.status_code, 200) - def test_delete_post_confirm_response_ok(self): forum = Forum.objects.first() thread = forum.thread_set.first() diff --git a/forum/urls.py b/forum/urls.py index b3c97b30e..55b5c1f91 100644 --- a/forum/urls.py +++ b/forum/urls.py @@ -38,6 +38,6 @@ re_path(r'^(?P[\w-]+)/(?P\d+)/(?P\d+)/reply/$', forum_views.reply, name="forums-reply-quote"), path('post//edit/', forum_views.post_edit, name="forums-post-edit"), - path('post//delete/', forum_views.post_delete, name="forums-post-delete"), + path('post//delete/', forum_views.post_delete, name="forums-post-delete"), # TODO: Delete after final switch to BW path('post//delete-confirm/', forum_views.post_delete_confirm, name="forums-post-delete-confirm"), ] diff --git a/forum/views.py b/forum/views.py index bf9c5c67d..92f99a9f1 100644 --- a/forum/views.py +++ b/forum/views.py @@ -261,7 +261,7 @@ def reply(request, forum_name_slug, thread_id, post_id=None): if users_to_notify and post.thread.get_status_display() != 'Sunk': send_mail_template( settings.EMAIL_SUBJECT_TOPIC_REPLY, - "forum/email_new_post_notification.txt", + "emails/email_new_post_notification.txt", {'post': post, 'thread': thread, 'forum': forum}, extra_subject=thread.title, user_to=users_to_notify, email_type_preference_check="new_post" diff --git a/freesound/settings.py b/freesound/settings.py index 53e58af9a..4c4865bc5 100644 --- a/freesound/settings.py +++ b/freesound/settings.py @@ -808,12 +808,12 @@ FRONTEND_SESSION_PARAM_NAME = 'frontend' FRONTEND_NIGHTINGALE = 'ng' # https://freesound.org/people/reinsamba/sounds/14854/ FRONTEND_BEASTWHOOSH = 'bw' # https://freesound.org/people/martian/sounds/403973/ -FRONTEND_DEFAULT = FRONTEND_NIGHTINGALE +FRONTEND_DEFAULT = FRONTEND_BEASTWHOOSH TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ - os.path.join(os.path.dirname(__file__), '../templates') + os.path.join(os.path.dirname(__file__), '../templates_bw'), ], 'APP_DIRS': True, 'OPTIONS': { @@ -829,12 +829,12 @@ 'freesound.context_processor.context_extra', ], }, - 'NAME': FRONTEND_NIGHTINGALE, + 'NAME': FRONTEND_BEASTWHOOSH, }, { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ - os.path.join(os.path.dirname(__file__), '../templates_bw'), + os.path.join(os.path.dirname(__file__), '../templates') ], 'APP_DIRS': True, 'OPTIONS': { @@ -850,9 +850,8 @@ 'freesound.context_processor.context_extra', ], }, - 'NAME': FRONTEND_BEASTWHOOSH, + 'NAME': FRONTEND_NIGHTINGALE, }, - ] # We use the last restart date as a timestamp of the last time freesound web was restarted (lat time diff --git a/freesound/static/bw-frontend/public/embeds/ajax_utils.js b/freesound/static/bw-frontend/public/embeds/ajax_utils.js new file mode 100644 index 000000000..266a6010e --- /dev/null +++ b/freesound/static/bw-frontend/public/embeds/ajax_utils.js @@ -0,0 +1,86 @@ +// For AJAX posts + +$(document).ajaxSend(function(event, xhr, settings) { + function getCookie(name) { + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } + function sameOrigin(url) { + // url could be relative or scheme relative or absolute + var host = document.location.host; // host + port + var protocol = document.location.protocol; + var sr_origin = '//' + host; + var origin = protocol + sr_origin; + // Allow absolute or scheme relative URLs to same origin + return (url == origin || url.slice(0, origin.length + 1) == origin + '/') || + (url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') || + // or any other URL that isn't scheme relative or absolute i.e relative. + !(/^(\/\/|http:|https:).*/.test(url)); + } + function safeMethod(method) { + return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); + } + + if (!safeMethod(settings.type) && sameOrigin(settings.url)) { + xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken')); + } +}); + +// For ajax get (as a substitute to GDownloadUrl from googlemaps) +function ajaxLoad(url,callback,postData,plain) { + var http_request = false; + + if (window.XMLHttpRequest) { // Mozilla, Safari, ... + http_request = new XMLHttpRequest(); + if (http_request.overrideMimeType && plain) { + http_request.overrideMimeType('text/plain'); + } + } else if (window.ActiveXObject) { // IE + try { + http_request = new ActiveXObject("Msxml2.XMLHTTP"); + } catch (e) { + try { + http_request = new ActiveXObject("Microsoft.XMLHTTP"); + } catch (e) {} + } + } + if (!http_request) { + //alert('Giving up :( Cannot create an XMLHTTP instance'); + console.log('Giving up :( Cannot create an XMLHTTP instance'); + return false; + } + http_request.onreadystatechange = function() { + if (http_request.readyState == 4) { + if (http_request.status == 200) { + eval(callback(http_request)); + } + else { + //alert('Request Failed: ' + http_request.status); + conosle.log('Request Failed: ' + http_request.status); + } + } + }; + + if (postData) { // POST + http_request.open('POST', url, true); + http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + http_request.setRequestHeader("Content-length", postData.length); + http_request.send(postData); + } + else { + http_request.open('GET', url, true); + http_request.send(null); + } +} + diff --git a/freesound/static/bw-frontend/public/embeds/html_player/embed.css b/freesound/static/bw-frontend/public/embeds/html_player/embed.css new file mode 100644 index 000000000..b8384ea8f --- /dev/null +++ b/freesound/static/bw-frontend/public/embeds/html_player/embed.css @@ -0,0 +1,323 @@ +a { + color: #2C74C9; + text-decoration: none; +} + +a:hover { + color: #19507E; + text-decoration: underline; +} + +h3 { + line-height: 1em; + margin-top: 7px; + margin-bottom: 9px; + font-size: 11px; + font-weight: bold; + color: #AB4646; +} + +.player.small { + display: inline; + float: left; + margin-right: 5px; +} + +#avatar { + display: inline; + float: left; + margin-right: 5px; +} + +body { + color: #333333; + margin: 0; + padding: 0; + font-family: Arial, sans-serif; + font-size:12px; +} + +/* Large */ + +body.large { + background: #F3F3F3 url(../images/widgets/large_bg.png) no-repeat top left; + font-family: Arial, sans-serif; +} + +.widget.large .player.large { + margin-left: 10px; + margin-top: 8px; +} + +.widget.large .sample_info { + width: 920px; +} + +.widget.large .sample_info h3 { + float: left; + margin-left: 10px; + margin-top: 10px; +} + +.widget.large .avatar { + margin-left: 14px; + margin-top: 7px; + width: 20px; + height: 20px; + float: left; +} + +.widget.large .sample_info .cc_license { + float: right; + margin-right: 12px; + margin-top: 8px; +/* display: block; */ +} + +.widget.large .sample_info a { + width: 88px; + height: 18px; + display: block; + float: right; + margin-right: 20px; + margin-top: 7px; + background-image: url(../images/widgets/see_on.png); +} + +.widget.large .sample_info a:hover { + background-image: url(../images/widgets/see_on_hover.png); +} + + +/* Medium */ + +body.medium { + background: #ffffff url(../images/widgets/medium_bg.png) no-repeat top left; + font-family: Arial, sans-serif; +} + +.widget.medium .player_wrapper { + float: left; + width: 120px; + padding: 8px; +} + +.widget.medium .avatar { + margin-right: 5px; + float: left; +} + +.widget.medium .title { + width: 100%; + overflow: hidden; +} + +.widget.medium .title img { + float: right; + margin-top: 6px; + margin-right: 6px; +} + +.widget.medium .title h3 { + width: 310px; + float: left; +} + +.widget.medium .sample_info_left { + width: 160px; + margin-left: 10px; + float: left; + margin-top: 8px; + overflow: hidden; +} + +.widget.medium .sample_info { + width: 345px; + float: left; +} + +.widget.medium .username { + float: left; + width: 115px; +} + +.widget.medium .sample_info_left a { + width: 88px; + height: 18px; + display: block; + float: left; + background-image: url(../images/widgets/see_on.png); +} + +.widget.medium .sample_info_left a:hover { + background-image: url(../images/widgets/see_on_hover.png); +} + +.widget.medium .sample_info_right { + width: 170px; + float: left; +} + +.widget.medium ul#tags { + padding: 0; + margin: 0; + line-height: 16px; + overflow-x: auto; + overflow-y: hidden; + height: 56px; +} + +.widget.medium #tags li { + display: block; + float: right; + margin: 2px; + height: 23px; + background-color: #F7F5D9; + border-bottom: solid 1px #D3C9AA; +} + +.widget.medium #tags li span { + display: block; + padding: 3px 6px 0 12px; + min-height: 21px; + font-size: 11px; + font-weight: bold; + background: url(../images/tag_edge.png) no-repeat left top; +} + +/* small */ + +body.small { + background: #ffffff url(../images/widgets/small_bg.png) no-repeat top left; +} + +.widget.small { + width: 365px; + margin-left: 5px; + margin-top: 5px; + height: 20px; +/* 370 */ +} + +.widget.small .player { + width: 40px; + float: left; +} + +.widget.small .sample_info { + width: 310px; + float: left; + margin-left: 10px; +} + +.widget.small .sample_info img { + width: 9px; + float: left; + margin-left: 6px; + margin-top: 5px; +} + +.widget.small h3 { + overflow: hidden; +/* width: 285px; */ + line-height: 20px; + margin-top: 0px; + float: left; +} + +.widget.small a { + display: block; + float: left; + text-decoration: none; + color: #AB4646; +} + +.widget.small a:hover { + text-decoration: underline; + color: #AB4646; +} + +/* full_size */ + +body.full_size { + border-bottom: solid 2px #AB4646; +} + +.widget.large.full_size .sample_info { + width: 100%; + border-top: solid 2px #AB4646; + /*padding-top: 5px;*/ +} + +.widget.large.full_size .sample_info .user_avatar { + width: 30px; + margin-left: 10px; + display: inline-block; +} + +.widget.large.full_size .sample_info .user_avatar .avatar { + margin-top: 0px; + margin-left: 3px; + float: none; +} + +.widget.large.full_size .sample_info .sound_name { + display: inline-block; + white-space: nowrap; + overflow: hidden; + width: calc(100% - 40px - 95px - 50px) +} + +.widget.large.full_size .sample_info .sound_name h3 { + line-height: 11px; + font-size: 11px; + font-weight: bold; + color: #AB4646; + margin-left: 0px; + margin-bottom: 3px; +} + +.widget.large.full_size .sample_info .freesound_link { + width:95px; + display: inline-block; +} + +.widget.large.full_size .sample_info .freesound_link a { + height: 18px; + float: none; + margin-right: 0px; + background-image: url(../images/widgets/see_on.png); +} + +.widget.large.full_size .sample_info .freesound_link a:hover { + background-image: url(../images/widgets/see_on_hover.png); +} + +.widget.large.full_size .sample_info .license { + width: 35px; + display: inline-block; +} + +.widget.large.full_size .sample_info .license .cc_license { + float: right; + margin-top: 0px; +} + +.widget.large.full_size .player.large { + width:100%; + /*height: 22vw;*/ + background-color: black; + margin-left: 0; + /*margin-top: 10px;*/ + /*padding-top: 14px;*/ + margin-bottom: 0px; +} +.widget.large.full_size .player { + margin-bottom: 0; + width:100%; + /*height: 26vw;*/ + background-color: black; + margin-left: 0; + margin-top: 4px; +} + diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_loop_icon.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_loop_icon.png new file mode 100755 index 000000000..6f6c97166 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_loop_icon.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_loop_icon_small.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_loop_icon_small.png new file mode 100755 index 000000000..6fc5e18bc Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_loop_icon_small.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_measure_icon.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_measure_icon.png new file mode 100755 index 000000000..c99200367 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_measure_icon.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_play_pause_icon.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_play_pause_icon.png new file mode 100644 index 000000000..dcc1f2836 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_play_pause_icon.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_play_pause_icon_small.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_play_pause_icon_small.png new file mode 100755 index 000000000..4f36c80b0 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_play_pause_icon_small.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_stop_icon.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_stop_icon.png new file mode 100755 index 000000000..3024b5534 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_stop_icon.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_waveform_spectral_icon.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_waveform_spectral_icon.png new file mode 100644 index 000000000..8d09e95ad Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_waveform_spectral_icon.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/b_waveform_spectral_icon_small.png b/freesound/static/bw-frontend/public/embeds/html_player/img/b_waveform_spectral_icon_small.png new file mode 100644 index 000000000..dbaa19c96 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/b_waveform_spectral_icon_small.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/icon_background.png b/freesound/static/bw-frontend/public/embeds/html_player/img/icon_background.png new file mode 100755 index 000000000..ad70b0008 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/icon_background.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/img/time_background.png b/freesound/static/bw-frontend/public/embeds/html_player/img/time_background.png new file mode 100644 index 000000000..42240c574 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/html_player/img/time_background.png differ diff --git a/freesound/static/bw-frontend/public/embeds/html_player/player.css b/freesound/static/bw-frontend/public/embeds/html_player/player.css new file mode 100644 index 000000000..fdc2fb053 --- /dev/null +++ b/freesound/static/bw-frontend/public/embeds/html_player/player.css @@ -0,0 +1,279 @@ +.player +{ + z-index: 1; + position: relative; + margin-bottom: 10px; +} + +.player.large +{ + width: 900px; + height: 201px; +} + +.player.small +{ + width: 120px; + height: 71px; +} + +.player.mini +{ + width: 40px; + height: 20px; + background-color: black; +} + +.player .metadata +{ + display:none; +} + +.player .background +{ + z-index: 2; + position: relative; +} + +.player .background +{ + height: 100%; + width: 100%; +} + +.player .position-indicator +{ + z-index: 3; + position: absolute; + top: 0; + left: 0; + width: 1px; + height: 100%; + background-color: white; + opacity: 0.6; +} + +.player .measure-readout +{ + z-index: 3; + position: absolute; + top: 0; + right: 0; + width: 200px; + height: 80px; + color: white; + font-size: 12px; + text-align: right; + font-family: 'Inconsolata', arial, serif; + padding-top:10px; + padding-right: 10px; +} + +.player .loading-progress +{ + z-index: 3; + position: absolute; + top: 0; + width: 100%; + height: 100%; + background-color: black; + opacity: 0.7; +} + +.player.large .time-indicator-container +{ + z-index: 5; + position: absolute; + background: url("img/time_background.png"); + background-repeat: no-repeat; + background-position: 44px 0px; + bottom: 0px; + right: 0px; + width: 150px; + height: 36px; + z-index: 6; + color: white; + font-size: 18px; + text-align: right; +} + +.player.large .time-indicator +{ + font-family: 'Inconsolata', arial, serif; + padding-top:10px; + padding-right: 10px; +} + +.player.small .time-indicator-container +{ + z-index: 5; + position: absolute; + /*background: url("img/time_background.png"); + background-repeat: no-repeat; + background-position: 44px 0px; */ + bottom: 1px; + right: 1px; + /*width: 150px;*/ + height: 13px; + z-index: 6; + color: white; + font-size: 13px; + text-align: right; +} + +.player.small .time-indicator +{ + font-family: 'Inconsolata', arial, serif; + padding-top: 0px; + padding-right: 0px; +} + + +/**************************/ + +.player .controls +{ + z-index: 5; + position: absolute; + bottom: 0px; +} + +.player.large .controls +{ + background: url("img/icon_background.png"); + background-repeat: no-repeat; + background-position: -110px 0px; + width: 190px; + height: 36px; +} + +.player.small .controls, .player.mini .controls +{ + width: 40px; + height: 20px; +} + +.player .controls > div , .player .controls > a +{ + z-index: 6; + background-repeat: no-repeat; + background-position: 0px 0px; + float: left; + font-size:0px; + line-height: 0; + text-indent:-9999px; +} + +.player.large .controls > div, .player.large .controls > a +{ + width: 36px; + height: 36px; +} + +.player.small .controls > div, .player.small .controls > a +{ + width: 20px; + height: 20px; +} + +.player.mini .controls > div, .player.mini .controls > a +{ + width: 20px; + height: 20px; +} + +/**************************/ + +.player .controls .button:hover +{ + background-position: 0px -36px; +} + +.player .controls .button:active +{ + background-position: 0px -72px; +} + +/**************************/ + +.player.large .controls .toggle +{ + background-position: 0px 0px; +} + +.player.large .controls .toggle:hover +{ + background-position: 0px -36px; +} + +.player.large .controls .toggle-alt +{ + background-position: 0px -72px; +} + +.player.large .controls .toggle-alt:hover +{ + background-position: 0px -108px; +} + + +.player.small .controls .toggle, .player.mini .controls .toggle +{ + background-position: 0px 0px; +} + +.player.small .controls .toggle:hover, .player.mini .controls .toggle:hover +{ + background-position: 0px -20px; +} + +.player.small .controls .toggle-alt, .player.mini .controls .toggle-alt +{ + background-position: 0px -40px; +} + +.player.small .controls .toggle-alt:hover, .player.mini .controls .toggle-alt:hover +{ + background-position: 0px -60px; +} +/**************************/ + +.player .stop +{ + background-image: url("img/b_stop_icon.png"); +} + +.player.large .play +{ + background-image: url("img/b_play_pause_icon.png"); +} + +.player.small .play, .player.mini .play +{ + background-image: url("img/b_play_pause_icon_small.png"); +} + +.player.large .display +{ + background-image: url("img/b_waveform_spectral_icon.png"); +} + +.player.small .display +{ + background-image: url("img/b_waveform_spectral_icon_small.png"); +} + +.player.large .loop +{ + background-image: url("img/b_loop_icon.png"); +} + +.player.small .loop, .player.mini .loop +{ + background-image: url("img/b_loop_icon_small.png"); +} + +.player .measure +{ + background-image: url("img/b_measure_icon.png"); +} diff --git a/freesound/static/bw-frontend/public/embeds/html_player/player.js b/freesound/static/bw-frontend/public/embeds/html_player/player.js new file mode 100644 index 000000000..17e490f52 --- /dev/null +++ b/freesound/static/bw-frontend/public/embeds/html_player/player.js @@ -0,0 +1,419 @@ +soundManager.useHTML5Audio = true; +soundManager.url = '/media/html_player/swf/'; +//flash 9 doesn't have weird artifacts at the beginning of sounds. +soundManager.flashVersion = 9; +soundManager.debugMode = false; +//soundManager.preferFlash = true; +// If the player is used in an embed, it uses HTML5 so it is lighter (althogh playbar position is not updated as fast as with flash) +//if (typeof isEmbed!="undefined"){ +// soundManager.preferFlash = false; +//} +//if you have a stricter test than 'maybe' SM will switch back to flash. +//soundManager.html5Test = /^maybe$/i + +$(function() +{ + $.extend($.fn.disableTextSelect = function() + { + return this.each(function() + { + if($.browser.mozilla) + { //Firefox + $(this).css('MozUserSelect','none'); + } + else if($.browser.msie) + { + //IE + $(this).bind('selectstart',function(){return false;}); + } + else + { + //Opera, etc. + $(this).mousedown(function(){return false;}); + } + }); + }); + $('.noSelect').disableTextSelect();//No text selection on elements with a class of 'noSelect' +}); + +function msToTime(position, durationEstimate, displayRemainingTime, showMs) +{ + if (displayRemainingTime) + position = durationEstimate - position; + + var ms = parseInt(position % 1000); + if (ms < 10) + ms = '00' + ms + else if (ms < 100) + ms = '0' + ms; + else + ms = '' + ms; + + var s = parseInt(position / 1000); + var seconds = parseInt(s % 60); + var minutes = parseInt(s / 60); + if (seconds < 10) + seconds = '0' + seconds; + else + seconds = '' + seconds; + + if (minutes < 10) + minutes = '0' + minutes; + else + minutes = '' + minutes; + + if (showMs) + return (displayRemainingTime ? "-" : " ") + minutes + ':' + seconds + ':' + ms; + else + return (displayRemainingTime ? "-" : " ") + minutes + ':' + seconds; +} + +var uniqueId = 0; +var _mapping = []; +var y_min = Math.log(100.0) / Math.LN10; +var y_max = Math.log(22050.0) / Math.LN10; + +for (var y = 200;y >= 0; y--) + _mapping.push(Math.pow(10.0, y_min + y / 200.0 * (y_max - y_min))); + +function switchToggle(element) +{ + if (element.hasClass("toggle")) + { + element.removeClass("toggle"); + element.addClass("toggle-alt"); + } + else if (element.hasClass("toggle-alt")) + { + element.removeClass("toggle-alt"); + element.addClass("toggle"); + } + element.toggleClass("on"); +} + + +function switchOff(element) +{ + element.addClass("toggle"); + element.removeClass("toggle-alt"); + element.removeClass("on"); +} + + +function switchOn(element) +{ + element.removeClass("toggle"); + element.addClass("toggle-alt"); + element.addClass("on"); +} + + +function getPlayerPosition(element) +{ + el = element[0]; + for (var lx=0, ly=0; + el != null; + lx += el.offsetLeft, ly += el.offsetTop, el = el.offsetParent); + return [lx, ly]; +} + + +function stopAll(exclude) +{ + var ids = soundManager.soundIDs; + ids = jQuery.grep(ids, function(value) + { + if(exclude) + return value != exclude.sID; + else + return true; + }); + switchOff($(".player .play")); + for(var i=0; i \ + play / pause \ + stop \ + change display \ + loop \ + toggle measure \ + \ +
\ +
\ +
\ +
\ +
'); + } + else if ($(this).hasClass("small")) + { + $(this).append('
\ + play / pause \ + loop \ +
\ +
\ +
\ +
\ +
'); + + // Check if toggle display button should be added and add it if requested + if (typeof showToggleDisplayButton !== "undefined"){ + if (showToggleDisplayButton){ + var toggle_display_button = 'change display'; + var cotrols_element = $('.controls'); + cotrols_element.css('width', '60px'); + cotrols_element.append(toggle_display_button); + } + } + } + else if ($(this).hasClass("mini")) { + $(this).append('
\ + play / pause \ + \ +
\ +
\ +
\ +
'); + } + + $("*", this).disableTextSelect(); + + var mp3Preview = $(".metadata .mp3_file", this).attr('href'); + var oggPreview = $(".metadata .ogg_file", this).attr('href'); + var waveform = $(".metadata .waveform", this).attr('href'); + var spectrum = $(".metadata .spectrum", this).attr('href'); + var duration = $(".metadata .duration", this).text(); + + var playerElement = $(this); + + if (!$(this).hasClass("mini")) + $(".background", this).css("backgroundImage", 'url("' + waveform + '")'); + $(".background", this).css("backgroundSize", 'contain'); + $(".background", this).css("backgroundRepeat", 'no-repeat'); + + $(".loading-progress", playerElement).hide(); + + $(".time-indicator", playerElement).html(msToTime(0, duration, !$(".time-indicator-container", playerElement).hasClass("on"), showMs)); + + if ($(this).hasClass("large")) + { + $(".controls", this).stop().fadeTo(10000, 0.2); + $(".measure-readout-container", this).stop().fadeTo(0, 0); + } + + // Ready to use; soundManager.createSound() etc. can now be called. + var sound = soundManager.createSound( + { + id: "sound-id-" + uniqueId++, + url: mp3Preview, + autoLoad: false, + autoPlay: false, + onload: function() + { + $(".loading-progress", playerElement).remove(); + }, + whileloading: function() + { + var loaded = this.bytesLoaded / this.bytesTotal * 100; + if(loaded > 0) $(".loading-progress", playerElement).show(); + $(".loading-progress", playerElement).css("width", (100 - loaded) + "%"); + $(".loading-progress", playerElement).css("left", loaded + "%"); + }, + whileplaying: function() + { + var positionPercent = this.position / duration * 100; + $(".position-indicator", playerElement).css("left", positionPercent + "%"); + $(".time-indicator", playerElement).html(msToTime(sound.position, duration, !$(".time-indicator-container", playerElement).hasClass("on"), showMs)); + }, + onfinish: function () + { + if ($(".loop", playerElement).hasClass("on")) + { + sound.play() + } + else + { + if ($(".play", playerElement).hasClass("on")) + switchToggle($(".play", playerElement)); + } + } + }); + + $(".play", this).bind("toggle", function (event, on) + { + if (on) + { + switchOn($(".play", playerElement)); + sound.play(); + } + else + { + switchOff($(".play", playerElement)); + sound.pause(); + } + }); + + $(".stop", this).click(function (event) + { + event.stopPropagation(); + if (sound.playState == 1 || !sound.paused) + { + sound.stop(); + sound.setPosition(0); + $(".time-indicator", playerElement).html( + msToTime(sound.position, + sound.duration, + !$(".time-indicator-container", playerElement).hasClass("on"), + showMs)); + $(".position-indicator", playerElement).css("left", "0%"); + switchOff($(".play", playerElement)); + } + }); + + $(".display", this).bind("toggle", function (event, on) + { + if (on) + $(".background", playerElement).css("background", "url(" + spectrum + ")"); + else + $(".background", playerElement).css("background", "url(" + waveform + ")"); + $(".background", playerElement).css("backgroundSize", 'contain'); + $(".background", playerElement).css("backgroundRepeat", 'no-repeat'); + }); + + $(".measure", this).bind("toggle", function (event, on) + { + if (on) + $(".measure-readout-container", playerElement).stop().fadeTo(100, 1.0); + else + $(".measure-readout-container", playerElement).stop().fadeTo(100, 0); + }); + + $(".background", this).click(function(event) + { + var pos = getMousePosition(event, $(this)); + sound.setPosition(duration * pos[0] / $(this).width()); + if (sound.playState == 0 || sound.paused) + { + sound.play(); + switchOn($(".play", playerElement)); + } + }); + + $(".time-indicator-container", this).click(function(event) + { + event.stopPropagation(); + $(this).toggleClass("on"); + }); + + $(this).hover(function() + { + if ($(this).hasClass("large")) + { + $(".controls", playerElement).stop().fadeTo(50, 1.0); + if ($(".measure", playerElement).hasClass("on")) + $(".measure-readout-container", playerElement).stop().fadeTo(50, 1.0); + } + },function() + { + if ($(this).hasClass("large")) + { + $(".controls", playerElement).stop().fadeTo(2000, 0.2); + if ($(".measure", playerElement).hasClass("on")) + $(".measure-readout-container", playerElement).stop().fadeTo(2000, 0.2); + } + }); + + $(this).mousemove(function (event) + { + var readout = ""; + pos = getMousePosition(event, $(this)); + + if ($(".display", playerElement).hasClass("on")) + { + readout = _mapping[Math.floor(pos[1])].toFixed(2) + "hz"; + } + else + { + var height2 = $(this).height()/2; + + if (pos[1] == height2) + readout = "-inf"; + else + readout = (20 * Math.log( Math.abs(pos[1]/height2 - 1) ) / Math.LN10).toFixed(2); + + readout = readout + " dB"; + } + + $('.measure-readout', playerElement).html(readout); + }); + + // Check if spectrogram image should be used by default + if (!$(this).hasClass("mini")) { + if (typeof spectrogramByDefault !== "undefined") { + if (spectrogramByDefault) { + var display_element = $('.display'); + if (display_element.length !== 0) { + // Switch to to background spectrogram by simulating click on toggle button + display_element.trigger('click'); + } else { + // Switch to to background spectrogram by replacing the image (toggle display button does not exist) + $(".background", playerElement).css("background", "url(" + spectrum + ")"); + $(".background", playerElement).css("backgroundSize", 'contain'); + $(".background", playerElement).css("backgroundRepeat", 'no-repeat'); + } + } + } + } + + return true; + }); +} + + +$(function() { + $(".toggle, .toggle-alt").live("click", function (event) + { + event.stopPropagation(); + switchToggle($(this)); + $(this).trigger("toggle", $(this).hasClass("on")); + }); + + soundManager.onready(function() + { + makePlayer('.player'); + }); +}); diff --git a/freesound/static/bw-frontend/public/embeds/html_player/soundmanager2.js b/freesound/static/bw-frontend/public/embeds/html_player/soundmanager2.js new file mode 100755 index 000000000..b20a3d35d --- /dev/null +++ b/freesound/static/bw-frontend/public/embeds/html_player/soundmanager2.js @@ -0,0 +1,112 @@ +/** @license + + + SoundManager 2: JavaScript Sound for the Web + ---------------------------------------------- + http://schillmania.com/projects/soundmanager2/ + + Copyright (c) 2007, Scott Schiller. All rights reserved. + Code provided under the BSD License: + http://schillmania.com/projects/soundmanager2/license.txt + + V2.97a.20140901 +*/ +(function(h,g){function Q(P,Q){function ha(b){return c.preferFlash&&F&&!c.ignoreFlash&&c.flash[b]!==g&&c.flash[b]}function r(b){return function(d){var e=this._s;!e||!e._a?(e&&e.id?c._wD(e.id+": Ignoring "+d.type):c._wD(sb+"Ignoring "+d.type),d=null):d=b.call(this,d);return d}}this.setupOptions={url:P||null,flashVersion:8,debugMode:!0,debugFlash:!1,useConsole:!0,consoleOnly:!0,waitForWindowLoad:!1,bgColor:"#ffffff",useHighPerformance:!1,flashPollingInterval:null,html5PollingInterval:null,flashLoadTimeout:1E3, +wmode:null,allowScriptAccess:"always",useFlashBlock:!1,useHTML5Audio:!0,html5Test:/^(probably|maybe)$/i,preferFlash:!1,noSWFCache:!1,idPrefix:"sound"};this.defaultOptions={autoLoad:!1,autoPlay:!1,from:null,loops:1,onid3:null,onload:null,whileloading:null,onplay:null,onpause:null,onresume:null,whileplaying:null,onposition:null,onstop:null,onfailure:null,onfinish:null,multiShot:!0,multiShotEvents:!1,position:null,pan:0,stream:!0,to:null,type:null,usePolicyFile:!1,volume:100};this.flash9Options={isMovieStar:null, +usePeakData:!1,useWaveformData:!1,useEQData:!1,onbufferchange:null,ondataerror:null};this.movieStarOptions={bufferTime:3,serverURL:null,onconnect:null,duration:null};this.audioFormats={mp3:{type:['audio/mpeg; codecs\x3d"mp3"',"audio/mpeg","audio/mp3","audio/MPA","audio/mpa-robust"],required:!0},mp4:{related:["aac","m4a","m4b"],type:['audio/mp4; codecs\x3d"mp4a.40.2"',"audio/aac","audio/x-m4a","audio/MP4A-LATM","audio/mpeg4-generic"],required:!1},ogg:{type:["audio/ogg; codecs\x3dvorbis"],required:!1}, +opus:{type:["audio/ogg; codecs\x3dopus","audio/opus"],required:!1},wav:{type:['audio/wav; codecs\x3d"1"',"audio/wav","audio/wave","audio/x-wav"],required:!1}};this.movieID="sm2-container";this.id=Q||"sm2movie";this.debugID="soundmanager-debug";this.debugURLParam=/([#?&])debug=1/i;this.versionNumber="V2.97a.20140901";this.altURL=this.movieURL=this.version=null;this.enabled=this.swfLoaded=!1;this.oMC=null;this.sounds={};this.soundIDs=[];this.didFlashBlock=this.muted=!1;this.filePattern=null;this.filePatterns= +{flash8:/\.mp3(\?.*)?$/i,flash9:/\.mp3(\?.*)?$/i};this.features={buffering:!1,peakData:!1,waveformData:!1,eqData:!1,movieStar:!1};this.sandbox={type:null,types:{remote:"remote (domain-based) rules",localWithFile:"local with file access (no internet access)",localWithNetwork:"local with network (internet access only, no local access)",localTrusted:"local, trusted (local+internet access)"},description:null,noRemote:null,noLocal:null};this.html5={usingFlash:null};this.flash={};this.ignoreFlash=this.html5Only= +!1;var Va,c=this,Wa=null,l=null,sb="HTML5::",y,u=navigator.userAgent,W=h.location.href.toString(),m=document,xa,Xa,ya,n,G=[],za=!0,B,X=!1,Y=!1,q=!1,x=!1,ia=!1,p,tb=0,Z,z,Aa,R,Ba,M,S,T,Ya,Ca,Da,ja,I,ka,Ea,N,Fa,$,la,ma,U,Za,Ga,$a=["log","info","warn","error"],ab,Ha,bb,aa=null,Ia=null,s,Ja,V,cb,na,oa,J,v,ba=!1,Ka=!1,db,eb,fb,pa=0,ca=null,qa,O=[],da,t=null,gb,ra,ea,K,sa,La,hb,w,ib=Array.prototype.slice,D=!1,Ma,F,Na,jb,H,kb,Oa,ta,lb=0,ua=u.match(/(ipad|iphone|ipod)/i),mb=u.match(/android/i),L=u.match(/msie/i), +ub=u.match(/webkit/i),va=u.match(/safari/i)&&!u.match(/chrome/i),Pa=u.match(/opera/i),Qa=u.match(/(mobile|pre\/|xoom)/i)||ua||mb,Ra=!W.match(/usehtml5audio/i)&&!W.match(/sm2\-ignorebadua/i)&&va&&!u.match(/silk/i)&&u.match(/OS X 10_6_([3-7])/i),fa=h.console!==g&&console.log!==g,Sa=m.hasFocus!==g?m.hasFocus():null,wa=va&&(m.hasFocus===g||!m.hasFocus()),nb=!wa,ob=/(mp3|mp4|mpa|m4a|m4b)/i,ga=m.location?m.location.protocol.match(/http/i):null,pb=!ga?"http://":"",qb=/^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i, +rb="mpeg4 aac flv mov mp4 m4v f4v m4a m4b mp4v 3gp 3g2".split(" "),vb=RegExp("\\.("+rb.join("|")+")(\\?.*)?$","i");this.mimePattern=/^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i;this.useAltURL=!ga;var Ta;try{Ta=Audio!==g&&(Pa&&opera!==g&&10>opera.version()?new Audio(null):new Audio).canPlayType!==g}catch(wb){Ta=!1}this.hasHTML5=Ta;this.setup=function(b){var d=!c.url;b!==g&&(q&&t&&c.ok()&&(b.flashVersion!==g||b.url!==g||b.html5Test!==g))&&J(s("setupLate"));Aa(b);b&&(d&&($&&b.url!==g)&&c.beginDelayedInit(), +!$&&(b.url!==g&&"complete"===m.readyState)&&setTimeout(N,1));return c};this.supported=this.ok=function(){return t?q&&!x:c.useHTML5Audio&&c.hasHTML5};this.getMovie=function(c){return y(c)||m[c]||h[c]};this.createSound=function(b,d){function e(){f=na(f);c.sounds[f.id]=new Va(f);c.soundIDs.push(f.id);return c.sounds[f.id]}var a,f;a=null;a="soundManager.createSound(): "+s(!q?"notReady":"notOK");if(!q||!c.ok())return J(a),!1;d!==g&&(b={id:b,url:d});f=z(b);f.url=qa(f.url);void 0===f.id&&(f.id=c.setupOptions.idPrefix+ +lb++);f.id.toString().charAt(0).match(/^[0-9]$/)&&c._wD("soundManager.createSound(): "+s("badID",f.id),2);c._wD("soundManager.createSound(): "+f.id+(f.url?" ("+f.url+")":""),1);if(v(f.id,!0))return c._wD("soundManager.createSound(): "+f.id+" exists",1),c.sounds[f.id];if(ra(f))a=e(),c._wD(f.id+": Using HTML5"),a._setup_html5(f);else{if(c.html5Only)return c._wD(f.id+": No HTML5 support for this sound, and no Flash. Exiting."),e();if(c.html5.usingFlash&&f.url&&f.url.match(/data\:/i))return c._wD(f.id+ +": data: URIs not supported via Flash. Exiting."),e();8a.instanceCount?(m(),e=a._setup_html5(),a.setPosition(a._iO.position),e.play()):(c._wD(a.id+": Cloning Audio() for instance #"+a.instanceCount+"..."),k=new Audio(a._iO.url),E=function(){w.remove(k,"ended",E);a._onfinish(a);sa(k);k=null},h= +function(){w.remove(k,"canplay",h);try{k.currentTime=a._iO.position/1E3}catch(c){J(a.id+": multiShot play() failed to apply position of "+a._iO.position/1E3)}k.play()},w.add(k,"ended",E),void 0!==a._iO.volume&&(k.volume=Math.max(0,Math.min(1,a._iO.volume/100))),a.muted&&(k.muted=!0),a._iO.position?w.add(k,"canplay",h):k.play()):(A=l._start(a.id,a._iO.loops||1,9===n?a.position:a.position/1E3,a._iO.multiShot||!1),9===n&&!A&&(c._wD(e+"No sound hardware, or 32-sound ceiling hit",2),a._iO.onplayerror&& +a._iO.onplayerror.apply(a)))}return a};this.stop=function(b){var d=a._iO;1===a.playState&&(c._wD(a.id+": stop()"),a._onbufferchange(0),a._resetOnPosition(0),a.paused=!1,a.isHTML5||(a.playState=0),Ua(),d.to&&a.clearOnPosition(d.to),a.isHTML5?a._a&&(b=a.position,a.setPosition(0),a.position=b,a._a.pause(),a.playState=0,a._onTimer(),E()):(l._stop(a.id,b),d.serverURL&&a.unload()),a.instanceCount=0,a._iO={},d.onstop&&d.onstop.apply(a));return a};this.setAutoPlay=function(b){c._wD(a.id+": Autoplay turned "+ +(b?"on":"off"));a._iO.autoPlay=b;a.isHTML5||(l._setAutoPlay(a.id,b),b&&(!a.instanceCount&&1===a.readyState)&&(a.instanceCount++,c._wD(a.id+": Incremented instance count to "+a.instanceCount)))};this.getAutoPlay=function(){return a._iO.autoPlay};this.setPosition=function(b){b===g&&(b=0);var d=a.isHTML5?Math.max(b,0):Math.min(a.duration||a._iO.duration,Math.max(b,0));a.position=d;b=a.position/1E3;a._resetOnPosition(a.position);a._iO.position=d;if(a.isHTML5){if(a._a){if(a._html5_canplay){if(a._a.currentTime!== +b){c._wD(a.id+": setPosition("+b+")");try{a._a.currentTime=b,(0===a.playState||a.paused)&&a._a.pause()}catch(e){c._wD(a.id+": setPosition("+b+") failed: "+e.message,2)}}}else if(b)return c._wD(a.id+": setPosition("+b+"): Cannot seek yet, sound not ready",2),a;a.paused&&a._onTimer(!0)}}else b=9===n?a.position:b,a.readyState&&2!==a.readyState&&l._setPosition(a.id,b,a.paused||!a.playState,a._iO.multiShot);return a};this.pause=function(b){if(a.paused||0===a.playState&&1!==a.readyState)return a;c._wD(a.id+ +": pause()");a.paused=!0;a.isHTML5?(a._setup_html5().pause(),E()):(b||b===g)&&l._pause(a.id,a._iO.multiShot);a._iO.onpause&&a._iO.onpause.apply(a);return a};this.resume=function(){var b=a._iO;if(!a.paused)return a;c._wD(a.id+": resume()");a.paused=!1;a.playState=1;a.isHTML5?(a._setup_html5().play(),m()):(b.isMovieStar&&!b.serverURL&&a.setPosition(a.position),l._pause(a.id,b.multiShot));!r&&b.onplay?(b.onplay.apply(a),r=!0):b.onresume&&b.onresume.apply(a);return a};this.togglePause=function(){c._wD(a.id+ +": togglePause()");if(0===a.playState)return a.play({position:9===n&&!a.isHTML5?a.position:a.position/1E3}),a;a.paused?a.resume():a.pause();return a};this.setPan=function(b,c){b===g&&(b=0);c===g&&(c=!1);a.isHTML5||l._setPan(a.id,b);a._iO.pan=b;c||(a.pan=b,a.options.pan=b);return a};this.setVolume=function(b,d){b===g&&(b=100);d===g&&(d=!1);a.isHTML5?a._a&&(c.muted&&!a.muted&&(a.muted=!0,a._a.muted=!0),a._a.volume=Math.max(0,Math.min(1,b/100))):l._setVolume(a.id,c.muted&&!a.muted||a.muted?0:b);a._iO.volume= +b;d||(a.volume=b,a.options.volume=b);return a};this.mute=function(){a.muted=!0;a.isHTML5?a._a&&(a._a.muted=!0):l._setVolume(a.id,0);return a};this.unmute=function(){a.muted=!1;var b=a._iO.volume!==g;a.isHTML5?a._a&&(a._a.muted=!1):l._setVolume(a.id,b?a._iO.volume:a.options.volume);return a};this.toggleMute=function(){return a.muted?a.unmute():a.mute()};this.onposition=this.onPosition=function(b,c,d){C.push({position:parseInt(b,10),method:c,scope:d!==g?d:a,fired:!1});return a};this.clearOnPosition= +function(a,b){var c;a=parseInt(a,10);if(isNaN(a))return!1;for(c=0;c=b)return!1;for(b-=1;0<=b;b--)c=C[b],!c.fired&&a.position>=c.position&&(c.fired=!0,u++,c.method.apply(c.scope,[c.position]));return!0};this._resetOnPosition=function(a){var b,c;b=C.length;if(!b)return!1;for(b-=1;0<=b;b--)c=C[b],c.fired&&a<=c.position&&(c.fired=!1,u--); +return!0};x=function(){var b=a._iO,d=b.from,e=b.to,f,g;g=function(){c._wD(a.id+': "To" time of '+e+" reached.");a.clearOnPosition(e,g);a.stop()};f=function(){c._wD(a.id+': Playing "from" '+d);if(null!==e&&!isNaN(e))a.onPosition(e,g)};null!==d&&!isNaN(d)&&(b.position=d,b.multiShot=!1,f());return b};q=function(){var b,c=a._iO.onposition;if(c)for(b in c)if(c.hasOwnProperty(b))a.onPosition(parseInt(b,10),c[b])};Ua=function(){var b,c=a._iO.onposition;if(c)for(b in c)c.hasOwnProperty(b)&&a.clearOnPosition(parseInt(b, +10))};m=function(){a.isHTML5&&db(a)};E=function(){a.isHTML5&&eb(a)};f=function(b){b||(C=[],u=0);r=!1;a._hasTimer=null;a._a=null;a._html5_canplay=!1;a.bytesLoaded=null;a.bytesTotal=null;a.duration=a._iO&&a._iO.duration?a._iO.duration:null;a.durationEstimate=null;a.buffered=[];a.eqData=[];a.eqData.left=[];a.eqData.right=[];a.failures=0;a.isBuffering=!1;a.instanceOptions={};a.instanceCount=0;a.loaded=!1;a.metadata={};a.readyState=0;a.muted=!1;a.paused=!1;a.peakData={left:0,right:0};a.waveformData={left:[], +right:[]};a.playState=0;a.position=null;a.id3={}};f();this._onTimer=function(b){var c,f=!1,g={};if(a._hasTimer||b){if(a._a&&(b||(0opera.version()?new Audio(null):new Audio,c=a._a,c._called_load=!1,D&&(Wa=c);a.isHTML5=!0;a._a=c;c._s=a;h();a._apply_loop(c,b.loops);b.autoLoad||b.autoPlay?a.load():(c.autobuffer=!1,c.preload="auto");return c};h=function(){if(a._a._added_events)return!1;var b;a._a._added_events=!0;for(b in H)H.hasOwnProperty(b)&&a._a&&a._a.addEventListener(b,H[b],!1);return!0};k=function(){var b;c._wD(a.id+": Removing event listeners");a._a._added_events=!1;for(b in H)H.hasOwnProperty(b)&&a._a&&a._a.removeEventListener(b, +H[b],!1)};this._onload=function(b){var d=!!b||!a.isHTML5&&8===n&&a.duration;b=a.id+": ";c._wD(b+(d?"onload()":"Failed to load / invalid sound?"+(!a.duration?" Zero-length duration reported.":" -")+" ("+a.url+")"),d?1:2);!d&&!a.isHTML5&&(!0===c.sandbox.noRemote&&c._wD(b+s("noNet"),1),!0===c.sandbox.noLocal&&c._wD(b+s("noLocal"),1));a.loaded=d;a.readyState=d?3:2;a._onbufferchange(0);a._iO.onload&&ta(a,function(){a._iO.onload.apply(a,[d])});return!0};this._onbufferchange=function(b){if(0===a.playState|| +b&&a.isBuffering||!b&&!a.isBuffering)return!1;a.isBuffering=1===b;a._iO.onbufferchange&&(c._wD(a.id+": Buffer state change: "+b),a._iO.onbufferchange.apply(a,[b]));return!0};this._onsuspend=function(){a._iO.onsuspend&&(c._wD(a.id+": Playback suspended"),a._iO.onsuspend.apply(a));return!0};this._onfailure=function(b,d,e){a.failures++;c._wD(a.id+": Failure ("+a.failures+"): "+b);if(a._iO.onfailure&&1===a.failures)a._iO.onfailure(b,d,e);else c._wD(a.id+": Ignoring failure")};this._onwarning=function(b, +c,d){if(a._iO.onwarning)a._iO.onwarning(b,c,d)};this._onfinish=function(){var b=a._iO.onfinish;a._onbufferchange(0);a._resetOnPosition(0);if(a.instanceCount&&(a.instanceCount--,a.instanceCount||(Ua(),a.playState=0,a.paused=!1,a.instanceCount=0,a.instanceOptions={},a._iO={},E(),a.isHTML5&&(a.position=0)),(!a.instanceCount||a._iO.multiShotEvents)&&b))c._wD(a.id+": onfinish()"),ta(a,function(){b.apply(a)})};this._whileloading=function(b,c,d,e){var f=a._iO;a.bytesLoaded=b;a.bytesTotal=c;a.duration=Math.floor(d); +a.bufferLength=e;a.durationEstimate=!a.isHTML5&&!f.isMovieStar?f.duration?a.duration>f.duration?a.duration:f.duration:parseInt(a.bytesTotal/a.bytesLoaded*a.duration,10):a.duration;a.isHTML5||(a.buffered=[{start:0,end:a.duration}]);(3!==a.readyState||a.isHTML5)&&f.whileloading&&f.whileloading.apply(a)};this._whileplaying=function(b,c,d,e,f){var k=a._iO;if(isNaN(b)||null===b)return!1;a.position=Math.max(0,b);a._processOnPosition();!a.isHTML5&&8opera.version()?new Audio(null):new Audio:null,e,a,f={},h,k;h=c.audioFormats;for(e in h)if(h.hasOwnProperty(e)&&(a="audio/"+e,f[e]=b(h[e].type),f[a]=f[e],e.match(ob)?(c.flash[e]=!0,c.flash[a]=!0):(c.flash[e]=!1,c.flash[a]=!1),h[e]&&h[e].related))for(k=h[e].related.length-1;0<=k;k--)f["audio/"+h[e].related[k]]=f[e],c.html5[h[e].related[k]]=f[e],c.flash[h[e].related[k]]=f[e];f.canPlayType=d?b:null;c.html5=z(c.html5,f);c.html5.usingFlash=gb();t=c.html5.usingFlash; +return!0};I={notReady:"Unavailable - wait until onready() has fired.",notOK:"Audio support is not available.",domError:"soundManagerexception caught while appending SWF to DOM.",spcWmode:"Removing wmode, preventing known SWF loading issue(s)",swf404:"soundManager: Verify that %s is a valid path.",tryDebug:"Try soundManager.debugFlash \x3d true for more security details (output goes to SWF.)",checkSWF:"See SWF output for more debug info.",localFail:"soundManager: Non-HTTP page ("+m.location.protocol+ +" URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/",waitFocus:"soundManager: Special case: Waiting for SWF to load with window focus...",waitForever:"soundManager: Waiting indefinitely for Flash (will recover if unblocked)...",waitSWF:"soundManager: Waiting for 100% SWF load...",needFunction:"soundManager: Function object expected for %s", +badID:'Sound ID "%s" should be a string, starting with a non-numeric character',currentObj:"soundManager: _debug(): Current sound objects",waitOnload:"soundManager: Waiting for window.onload()",docLoaded:"soundManager: Document already loaded",onload:"soundManager: initComplete(): calling soundManager.onload()",onloadOK:"soundManager.onload() complete",didInit:"soundManager: init(): Already called?",secNote:"Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html", +badRemove:"soundManager: Failed to remove Flash node.",shutdown:"soundManager.disable(): Shutting down",queue:"soundManager: Queueing %s handler",smError:"SMSound.load(): Exception: JS-Flash communication failed, or JS error.",fbTimeout:"No flash response, applying .swf_timedout CSS...",fbLoaded:"Flash loaded",fbHandler:"soundManager: flashBlockHandler()",manURL:"SMSound.load(): Using manually-assigned URL",onURL:"soundManager.load(): current URL already assigned.",badFV:'soundManager.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.', +as2loop:"Note: Setting stream:false so looping can work (flash 8 limitation)",noNSLoop:"Note: Looping not implemented for MovieStar formats",needfl9:"Note: Switching to flash 9, required for MP4 formats.",mfTimeout:"Setting flashLoadTimeout \x3d 0 (infinite) for off-screen, mobile flash case",needFlash:"soundManager: Fatal error: Flash is needed to play some required formats, but is not available.",gotFocus:"soundManager: Got window focus.",policy:"Enabling usePolicyFile for data access",setup:"soundManager.setup(): allowed parameters: %s", +setupError:'soundManager.setup(): "%s" cannot be assigned with this method.',setupUndef:'soundManager.setup(): Could not find option "%s"',setupLate:"soundManager.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().",noURL:"soundManager: Flash URL required. Call soundManager.setup({url:...}) to get started.",sm2Loaded:"SoundManager 2: Ready. "+String.fromCharCode(10003),reset:"soundManager.reset(): Removing event callbacks",mobileUA:"Mobile UA detected, preferring HTML5 by default.", +globalHTML5:"Using singleton HTML5 Audio() pattern for this device."};s=function(){var b,c,e,a;b=ib.call(arguments);c=b.shift();if((a=I&&I[c]?I[c]:"")&&b&&b.length){c=0;for(e=b.length;cn)&&(c._wD(s("needfl9")),c.flashVersion=n=9);c.version=c.versionNumber+(c.html5Only?" (HTML5-only mode)":9===n?" (AS3/Flash 9)":" (AS2/Flash 8)");8b&&(d=!0));setTimeout(function(){b=c.getMoviePercent();if(d)return ba=!1,c._wD(s("waitSWF")),h.setTimeout(T,1),!1;q||(c._wD("soundManager: No Flash response within expected time. Likely causes: "+(0===b?"SWF load failed, ":"")+"Flash blocked or JS-Flash security error."+(c.debugFlash? +" "+s("checkSWF"):""),2),!ga&&b&&(p("localFail",2),c.debugFlash||p("tryDebug",2)),0===b&&c._wD(s("swf404",c.url),1),B("flashtojs",!1,": Timed out"+ga?" (Check flash security or flash blockers)":" (No plugin/missing SWF?)"));!q&&nb&&(null===b?c.useFlashBlock||0===c.flashLoadTimeout?(c.useFlashBlock&&Ja(),p("waitForever")):!c.useFlashBlock&&da?Ca():(p("waitForever"),M({type:"ontimeout",ignoreInit:!0,error:{type:"INIT_FLASHBLOCK"}})):0===c.flashLoadTimeout?p("waitForever"):!c.useFlashBlock&&da?Ca(): +Ha(!0))},c.flashLoadTimeout)};ja=function(){if(Sa||!wa)return w.remove(h,"focus",ja),!0;Sa=nb=!0;p("gotFocus");ba=!1;T();w.remove(h,"focus",ja);return!0};Oa=function(){O.length&&(c._wD("SoundManager 2: "+O.join(" "),1),O=[])};kb=function(){Oa();var b,d=[];if(c.useHTML5Audio&&c.hasHTML5){for(b in c.audioFormats)c.audioFormats.hasOwnProperty(b)&&d.push(b+" \x3d "+c.html5[b]+(!c.html5[b]&&t&&c.flash[b]?" (using flash)":c.preferFlash&&c.flash[b]&&t?" (preferring flash)":!c.html5[b]?" ("+(c.audioFormats[b].required? +"required, ":"")+"and no flash support)":""));c._wD("SoundManager 2 HTML5 support: "+d.join(", "),1)}};Z=function(b){if(q)return!1;if(c.html5Only)return p("sm2Loaded",1),q=!0,S(),B("onload",!0),!0;var d=!0,e;if(!c.useFlashBlock||!c.flashLoadTimeout||c.getMoviePercent())q=!0;e={type:!F&&t?"NO_FLASH":"INIT_TIMEOUT"};c._wD("SoundManager 2 "+(x?"failed to load":"loaded")+" ("+(x?"Flash security/load error":"OK")+") "+String.fromCharCode(x?10006:10003),x?2:1);x||b?(c.useFlashBlock&&c.oMC&&(c.oMC.className= +V()+" "+(null===c.getMoviePercent()?"swf_timedout":"swf_error")),M({type:"ontimeout",error:e,ignoreInit:!0}),B("onload",!1),U(e),d=!1):B("onload",!0);x||(c.waitForWindowLoad&&!ia?(p("waitOnload"),w.add(h,"load",S)):(c.waitForWindowLoad&&ia&&p("docLoaded"),S()));return d};Xa=function(){var b,d=c.setupOptions;for(b in d)d.hasOwnProperty(b)&&(c[b]===g?c[b]=d[b]:c[b]!==d[b]&&(c.setupOptions[b]=c[b]))};ya=function(){if(q)return p("didInit"),!1;if(c.html5Only)return q||(w.remove(h,"load",c.beginDelayedInit), +c.enabled=!0,Z()),!0;ka();try{l._externalInterfaceTest(!1),Za(!0,c.flashPollingInterval||(c.useHighPerformance?10:50)),c.debugMode||l._disableDebug(),c.enabled=!0,B("jstoflash",!0),c.html5Only||w.add(h,"unload",xa)}catch(b){return c._wD("js/flash exception: "+b.toString()),B("jstoflash",!1),U({type:"JS_TO_FLASH_EXCEPTION",fatal:!0}),Ha(!0),Z(),!1}Z();w.remove(h,"load",c.beginDelayedInit);return!0};N=function(){if($)return!1;$=!0;Xa();Ga();var b=null,b=null,d=W.toLowerCase();-1!==d.indexOf("sm2-usehtml5audio\x3d")&& +(b="1"===d.charAt(d.indexOf("sm2-usehtml5audio\x3d")+18),fa&&console.log((b?"Enabling ":"Disabling ")+"useHTML5Audio via URL parameter"),c.setup({useHTML5Audio:b}));-1!==d.indexOf("sm2-preferflash\x3d")&&(b="1"===d.charAt(d.indexOf("sm2-preferflash\x3d")+16),fa&&console.log((b?"Enabling ":"Disabling ")+"preferFlash via URL parameter"),c.setup({preferFlash:b}));!F&&c.hasHTML5&&(c._wD("SoundManager 2: No Flash detected"+(!c.useHTML5Audio?", enabling HTML5.":". Trying HTML5-only mode."),1),c.setup({useHTML5Audio:!0, +preferFlash:!1}));hb();!F&&t&&(O.push(I.needFlash),c.setup({flashLoadTimeout:1}));m.removeEventListener&&m.removeEventListener("DOMContentLoaded",N,!1);ka();return!0};La=function(){"complete"===m.readyState&&(N(),m.detachEvent("onreadystatechange",La));return!0};Fa=function(){ia=!0;N();w.remove(h,"load",Fa)};Ea=function(){if(Qa&&((!c.setupOptions.useHTML5Audio||c.setupOptions.preferFlash)&&O.push(I.mobileUA),c.setupOptions.useHTML5Audio=!0,c.setupOptions.preferFlash=!1,ua||mb&&!u.match(/android\s2\.3/i)))O.push(I.globalHTML5), +ua&&(c.ignoreFlash=!0),D=!0};Ea();Na();w.add(h,"focus",ja);w.add(h,"load",T);w.add(h,"load",Fa);m.addEventListener?m.addEventListener("DOMContentLoaded",N,!1):m.attachEvent?m.attachEvent("onreadystatechange",La):(B("onload",!1),U({type:"NO_DOM2_EVENTS",fatal:!0}))}if(!h||!h.document)throw Error("SoundManager requires a browser with window and document objects.");var P=null;if(void 0===h.SM2_DEFER||!SM2_DEFER)P=new Q;"object"===typeof module&&module&&"object"===typeof module.exports?(h.soundManager= +P,module.exports.SoundManager=Q,module.exports.soundManager=P):"function"===typeof define&&define.amd?define("SoundManager",[],function(){return{SoundManager:Q,soundManager:P}}):(h.SoundManager=Q,h.soundManager=P)})(window); \ No newline at end of file diff --git a/freesound/static/bw-frontend/public/embeds/images/map_marker.png b/freesound/static/bw-frontend/public/embeds/images/map_marker.png new file mode 100644 index 000000000..16edb8f88 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/map_marker.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/tag_edge.png b/freesound/static/bw-frontend/public/embeds/images/tag_edge.png new file mode 100644 index 000000000..f7efea10a Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/tag_edge.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/by.png b/freesound/static/bw-frontend/public/embeds/images/widgets/by.png new file mode 100644 index 000000000..ee95a43dc Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/by.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/bync.png b/freesound/static/bw-frontend/public/embeds/images/widgets/bync.png new file mode 100644 index 000000000..18d7ffa64 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/bync.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large.png new file mode 100644 index 000000000..abe588e98 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_active.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_active.png new file mode 100644 index 000000000..5970e20ff Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_active.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_active_color.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_active_color.png new file mode 100644 index 000000000..44d12204b Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_active_color.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_inactive.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_inactive.png new file mode 100644 index 000000000..3dce93d94 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_large_scaled_inactive.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium.png new file mode 100644 index 000000000..0f8e91cd6 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_active.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_active.png new file mode 100644 index 000000000..6075132c2 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_active.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_active_color.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_active_color.png new file mode 100644 index 000000000..173ad085b Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_active_color.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_inactive.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_inactive.png new file mode 100644 index 000000000..1e452088a Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_medium_scaled_inactive.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small.png new file mode 100644 index 000000000..b60d05bb0 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_active.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_active.png new file mode 100644 index 000000000..7a209855c Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_active.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_active_color.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_active_color.png new file mode 100644 index 000000000..950735b92 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_active_color.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_inactive.png b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_inactive.png new file mode 100644 index 000000000..1000a215d Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/embed_small_scaled_inactive.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/external.png b/freesound/static/bw-frontend/public/embeds/images/widgets/external.png new file mode 100644 index 000000000..3ba47b399 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/external.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/large_bg.png b/freesound/static/bw-frontend/public/embeds/images/widgets/large_bg.png new file mode 100644 index 000000000..4abe1e99b Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/large_bg.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/medium_bg.png b/freesound/static/bw-frontend/public/embeds/images/widgets/medium_bg.png new file mode 100644 index 000000000..531b989d4 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/medium_bg.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/nolaw.png b/freesound/static/bw-frontend/public/embeds/images/widgets/nolaw.png new file mode 100644 index 000000000..eb19cbfc1 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/nolaw.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/sampling.png b/freesound/static/bw-frontend/public/embeds/images/widgets/sampling.png new file mode 100644 index 000000000..8e49cc456 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/sampling.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/see_on.png b/freesound/static/bw-frontend/public/embeds/images/widgets/see_on.png new file mode 100644 index 000000000..1c488001b Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/see_on.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/see_on_hover.png b/freesound/static/bw-frontend/public/embeds/images/widgets/see_on_hover.png new file mode 100644 index 000000000..aebc0a9c1 Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/see_on_hover.png differ diff --git a/freesound/static/bw-frontend/public/embeds/images/widgets/small_bg.png b/freesound/static/bw-frontend/public/embeds/images/widgets/small_bg.png new file mode 100644 index 000000000..5228aa5ea Binary files /dev/null and b/freesound/static/bw-frontend/public/embeds/images/widgets/small_bg.png differ diff --git a/freesound/static/bw-frontend/public/embeds/jquery-1.5.2.min.js b/freesound/static/bw-frontend/public/embeds/jquery-1.5.2.min.js new file mode 100644 index 000000000..f78f96a12 --- /dev/null +++ b/freesound/static/bw-frontend/public/embeds/jquery-1.5.2.min.js @@ -0,0 +1,16 @@ +/*! + * jQuery JavaScript Library v1.5.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Thu Mar 31 15:28:23 2011 -0400 + */ +(function(a,b){function ci(a){return d.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cf(a){if(!b_[a]){var b=d("<"+a+">").appendTo("body"),c=b.css("display");b.remove();if(c==="none"||c==="")c="block";b_[a]=c}return b_[a]}function ce(a,b){var c={};d.each(cd.concat.apply([],cd.slice(0,b)),function(){c[this]=a});return c}function b$(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function bZ(){try{return new a.XMLHttpRequest}catch(b){}}function bY(){d(a).unload(function(){for(var a in bW)bW[a](0,1)})}function bS(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var e=a.dataTypes,f={},g,h,i=e.length,j,k=e[0],l,m,n,o,p;for(g=1;g=0===c})}function P(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function H(a,b){return(a&&a!=="*"?a+".":"")+b.replace(t,"`").replace(u,"&")}function G(a){var b,c,e,f,g,h,i,j,k,l,m,n,o,p=[],q=[],s=d._data(this,"events");if(a.liveFired!==this&&s&&s.live&&!a.target.disabled&&(!a.button||a.type!=="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var t=s.live.slice(0);for(i=0;ic)break;a.currentTarget=f.elem,a.data=f.handleObj.data,a.handleObj=f.handleObj,o=f.handleObj.origHandler.apply(f.elem,arguments);if(o===!1||a.isPropagationStopped()){c=f.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function E(a,c,e){var f=d.extend({},e[0]);f.type=a,f.originalEvent={},f.liveFired=b,d.event.handle.call(c,f),f.isDefaultPrevented()&&e[0].preventDefault()}function y(){return!0}function x(){return!1}function i(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function h(a,c,e){if(e===b&&a.nodeType===1){e=a.getAttribute("data-"+c);if(typeof e==="string"){try{e=e==="true"?!0:e==="false"?!1:e==="null"?null:d.isNaN(e)?g.test(e)?d.parseJSON(e):e:parseFloat(e)}catch(f){}d.data(a,c,e)}else e=b}return e}var c=a.document,d=function(){function G(){if(!d.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(G,1);return}d.ready()}}var d=function(a,b){return new d.fn.init(a,b,g)},e=a.jQuery,f=a.$,g,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,i=/\S/,j=/^\s+/,k=/\s+$/,l=/\d/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=navigator.userAgent,w,x,y,z=Object.prototype.toString,A=Object.prototype.hasOwnProperty,B=Array.prototype.push,C=Array.prototype.slice,D=String.prototype.trim,E=Array.prototype.indexOf,F={};d.fn=d.prototype={constructor:d,init:function(a,e,f){var g,i,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!e&&c.body){this.context=c,this[0]=c.body,this.selector="body",this.length=1;return this}if(typeof a==="string"){g=h.exec(a);if(!g||!g[1]&&e)return!e||e.jquery?(e||f).find(a):this.constructor(e).find(a);if(g[1]){e=e instanceof d?e[0]:e,k=e?e.ownerDocument||e:c,j=m.exec(a),j?d.isPlainObject(e)?(a=[c.createElement(j[1])],d.fn.attr.call(a,e,!0)):a=[k.createElement(j[1])]:(j=d.buildFragment([g[1]],[k]),a=(j.cacheable?d.clone(j.fragment):j.fragment).childNodes);return d.merge(this,a)}i=c.getElementById(g[2]);if(i&&i.parentNode){if(i.id!==g[2])return f.find(a);this.length=1,this[0]=i}this.context=c,this.selector=a;return this}if(d.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return d.makeArray(a,this)},selector:"",jquery:"1.5.2",length:0,size:function(){return this.length},toArray:function(){return C.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var e=this.constructor();d.isArray(a)?B.apply(e,a):d.merge(e,a),e.prevObject=this,e.context=this.context,b==="find"?e.selector=this.selector+(this.selector?" ":"")+c:b&&(e.selector=this.selector+"."+b+"("+c+")");return e},each:function(a,b){return d.each(this,a,b)},ready:function(a){d.bindReady(),x.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(C.apply(this,arguments),"slice",C.call(arguments).join(","))},map:function(a){return this.pushStack(d.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:B,sort:[].sort,splice:[].splice},d.fn.init.prototype=d.fn,d.extend=d.fn.extend=function(){var a,c,e,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i==="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!=="object"&&!d.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;x.resolveWith(c,[d]),d.fn.trigger&&d(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!x){x=d._Deferred();if(c.readyState==="complete")return setTimeout(d.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",y,!1),a.addEventListener("load",d.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",y),a.attachEvent("onload",d.ready);var b=!1;try{b=a.frameElement==null}catch(e){}c.documentElement.doScroll&&b&&G()}}},isFunction:function(a){return d.type(a)==="function"},isArray:Array.isArray||function(a){return d.type(a)==="array"},isWindow:function(a){return a&&typeof a==="object"&&"setInterval"in a},isNaN:function(a){return a==null||!l.test(a)||isNaN(a)},type:function(a){return a==null?String(a):F[z.call(a)]||"object"},isPlainObject:function(a){if(!a||d.type(a)!=="object"||a.nodeType||d.isWindow(a))return!1;if(a.constructor&&!A.call(a,"constructor")&&!A.call(a.constructor.prototype,"isPrototypeOf"))return!1;var c;for(c in a){}return c===b||A.call(a,c)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!=="string"||!b)return null;b=d.trim(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return a.JSON&&a.JSON.parse?a.JSON.parse(b):(new Function("return "+b))();d.error("Invalid JSON: "+b)},parseXML:function(b,c,e){a.DOMParser?(e=new DOMParser,c=e.parseFromString(b,"text/xml")):(c=new ActiveXObject("Microsoft.XMLDOM"),c.async="false",c.loadXML(b)),e=c.documentElement,(!e||!e.nodeName||e.nodeName==="parsererror")&&d.error("Invalid XML: "+b);return c},noop:function(){},globalEval:function(a){if(a&&i.test(a)){var b=c.head||c.getElementsByTagName("head")[0]||c.documentElement,e=c.createElement("script");d.support.scriptEval()?e.appendChild(c.createTextNode(a)):e.text=a,b.insertBefore(e,b.firstChild),b.removeChild(e)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,e){var f,g=0,h=a.length,i=h===b||d.isFunction(a);if(e){if(i){for(f in a)if(c.apply(a[f],e)===!1)break}else for(;g1?f.call(arguments,0):c,--g||h.resolveWith(h,f.call(b,0))}}var b=arguments,c=0,e=b.length,g=e,h=e<=1&&a&&d.isFunction(a.promise)?a:d.Deferred();if(e>1){for(;c
a";var e=b.getElementsByTagName("*"),f=b.getElementsByTagName("a")[0],g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=b.getElementsByTagName("input")[0];if(e&&e.length&&f){d.support={leadingWhitespace:b.firstChild.nodeType===3,tbody:!b.getElementsByTagName("tbody").length,htmlSerialize:!!b.getElementsByTagName("link").length,style:/red/.test(f.getAttribute("style")),hrefNormalized:f.getAttribute("href")==="/a",opacity:/^0.55$/.test(f.style.opacity),cssFloat:!!f.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,deleteExpando:!0,optDisabled:!1,checkClone:!1,noCloneEvent:!0,noCloneChecked:!0,boxModel:null,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableHiddenOffsets:!0,reliableMarginRight:!0},i.checked=!0,d.support.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,d.support.optDisabled=!h.disabled;var j=null;d.support.scriptEval=function(){if(j===null){var b=c.documentElement,e=c.createElement("script"),f="script"+d.now();try{e.appendChild(c.createTextNode("window."+f+"=1;"))}catch(g){}b.insertBefore(e,b.firstChild),a[f]?(j=!0,delete a[f]):j=!1,b.removeChild(e)}return j};try{delete b.test}catch(k){d.support.deleteExpando=!1}!b.addEventListener&&b.attachEvent&&b.fireEvent&&(b.attachEvent("onclick",function l(){d.support.noCloneEvent=!1,b.detachEvent("onclick",l)}),b.cloneNode(!0).fireEvent("onclick")),b=c.createElement("div"),b.innerHTML="";var m=c.createDocumentFragment();m.appendChild(b.firstChild),d.support.checkClone=m.cloneNode(!0).cloneNode(!0).lastChild.checked,d(function(){var a=c.createElement("div"),b=c.getElementsByTagName("body")[0];if(b){a.style.width=a.style.paddingLeft="1px",b.appendChild(a),d.boxModel=d.support.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,d.support.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="
",d.support.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
t
";var e=a.getElementsByTagName("td");d.support.reliableHiddenOffsets=e[0].offsetHeight===0,e[0].style.display="",e[1].style.display="none",d.support.reliableHiddenOffsets=d.support.reliableHiddenOffsets&&e[0].offsetHeight===0,a.innerHTML="",c.defaultView&&c.defaultView.getComputedStyle&&(a.style.width="1px",a.style.marginRight="0",d.support.reliableMarginRight=(parseInt(c.defaultView.getComputedStyle(a,null).marginRight,10)||0)===0),b.removeChild(a).style.display="none",a=e=null}});var n=function(a){var b=c.createElement("div");a="on"+a;if(!b.attachEvent)return!0;var d=a in b;d||(b.setAttribute(a,"return;"),d=typeof b[a]==="function");return d};d.support.submitBubbles=n("submit"),d.support.changeBubbles=n("change"),b=e=f=null}}();var g=/^(?:\{.*\}|\[.*\])$/;d.extend({cache:{},uuid:0,expando:"jQuery"+(d.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?d.cache[a[d.expando]]:a[d.expando];return!!a&&!i(a)},data:function(a,c,e,f){if(d.acceptData(a)){var g=d.expando,h=typeof c==="string",i,j=a.nodeType,k=j?d.cache:a,l=j?a[d.expando]:a[d.expando]&&d.expando;if((!l||f&&l&&!k[l][g])&&h&&e===b)return;l||(j?a[d.expando]=l=++d.uuid:l=d.expando),k[l]||(k[l]={},j||(k[l].toJSON=d.noop));if(typeof c==="object"||typeof c==="function")f?k[l][g]=d.extend(k[l][g],c):k[l]=d.extend(k[l],c);i=k[l],f&&(i[g]||(i[g]={}),i=i[g]),e!==b&&(i[c]=e);if(c==="events"&&!i[c])return i[g]&&i[g].events;return h?i[c]:i}},removeData:function(b,c,e){if(d.acceptData(b)){var f=d.expando,g=b.nodeType,h=g?d.cache:b,j=g?b[d.expando]:d.expando;if(!h[j])return;if(c){var k=e?h[j][f]:h[j];if(k){delete k[c];if(!i(k))return}}if(e){delete h[j][f];if(!i(h[j]))return}var l=h[j][f];d.support.deleteExpando||h!=a?delete h[j]:h[j]=null,l?(h[j]={},g||(h[j].toJSON=d.noop),h[j][f]=l):g&&(d.support.deleteExpando?delete b[d.expando]:b.removeAttribute?b.removeAttribute(d.expando):b[d.expando]=null)}},_data:function(a,b,c){return d.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=d.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),d.fn.extend({data:function(a,c){var e=null;if(typeof a==="undefined"){if(this.length){e=d.data(this[0]);if(this[0].nodeType===1){var f=this[0].attributes,g;for(var i=0,j=f.length;i-1)return!0;return!1},val:function(a){if(!arguments.length){var c=this[0];if(c){if(d.nodeName(c,"option")){var e=c.attributes.value;return!e||e.specified?c.value:c.text}if(d.nodeName(c,"select")){var f=c.selectedIndex,g=[],h=c.options,i=c.type==="select-one";if(f<0)return null;for(var j=i?f:0,k=i?f+1:h.length;j=0;else if(d.nodeName(this,"select")){var f=d.makeArray(e);d("option",this).each(function(){this.selected=d.inArray(d(this).val(),f)>=0}),f.length||(this.selectedIndex=-1)}else this.value=e}})}}),d.extend({attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,e,f){if(!a||a.nodeType===3||a.nodeType===8||a.nodeType===2)return b;if(f&&c in d.attrFn)return d(a)[c](e);var g=a.nodeType!==1||!d.isXMLDoc(a),h=e!==b;c=g&&d.props[c]||c;if(a.nodeType===1){var i=m.test(c);if(c==="selected"&&!d.support.optSelected){var j=a.parentNode;j&&(j.selectedIndex,j.parentNode&&j.parentNode.selectedIndex)}if((c in a||a[c]!==b)&&g&&!i){h&&(c==="type"&&n.test(a.nodeName)&&a.parentNode&&d.error("type property can't be changed"),e===null?a.nodeType===1&&a.removeAttribute(c):a[c]=e);if(d.nodeName(a,"form")&&a.getAttributeNode(c))return a.getAttributeNode(c).nodeValue;if(c==="tabIndex"){var k=a.getAttributeNode("tabIndex");return k&&k.specified?k.value:o.test(a.nodeName)||p.test(a.nodeName)&&a.href?0:b}return a[c]}if(!d.support.style&&g&&c==="style"){h&&(a.style.cssText=""+e);return a.style.cssText}h&&a.setAttribute(c,""+e);if(!a.attributes[c]&&(a.hasAttribute&&!a.hasAttribute(c)))return b;var l=!d.support.hrefNormalized&&g&&i?a.getAttribute(c,2):a.getAttribute(c);return l===null?b:l}h&&(a[c]=e);return a[c]}});var r=/\.(.*)$/,s=/^(?:textarea|input|select)$/i,t=/\./g,u=/ /g,v=/[^\w\s.|`]/g,w=function(a){return a.replace(v,"\\$&")};d.event={add:function(c,e,f,g){if(c.nodeType!==3&&c.nodeType!==8){try{d.isWindow(c)&&(c!==a&&!c.frameElement)&&(c=a)}catch(h){}if(f===!1)f=x;else if(!f)return;var i,j;f.handler&&(i=f,f=i.handler),f.guid||(f.guid=d.guid++);var k=d._data(c);if(!k)return;var l=k.events,m=k.handle;l||(k.events=l={}),m||(k.handle=m=function(a){return typeof d!=="undefined"&&d.event.triggered!==a.type?d.event.handle.apply(m.elem,arguments):b}),m.elem=c,e=e.split(" ");var n,o=0,p;while(n=e[o++]){j=i?d.extend({},i):{handler:f,data:g},n.indexOf(".")>-1?(p=n.split("."),n=p.shift(),j.namespace=p.slice(0).sort().join(".")):(p=[],j.namespace=""),j.type=n,j.guid||(j.guid=f.guid);var q=l[n],r=d.event.special[n]||{};if(!q){q=l[n]=[];if(!r.setup||r.setup.call(c,g,p,m)===!1)c.addEventListener?c.addEventListener(n,m,!1):c.attachEvent&&c.attachEvent("on"+n,m)}r.add&&(r.add.call(c,j),j.handler.guid||(j.handler.guid=f.guid)),q.push(j),d.event.global[n]=!0}c=null}},global:{},remove:function(a,c,e,f){if(a.nodeType!==3&&a.nodeType!==8){e===!1&&(e=x);var g,h,i,j,k=0,l,m,n,o,p,q,r,s=d.hasData(a)&&d._data(a),t=s&&s.events;if(!s||!t)return;c&&c.type&&(e=c.handler,c=c.type);if(!c||typeof c==="string"&&c.charAt(0)==="."){c=c||"";for(h in t)d.event.remove(a,h+c);return}c=c.split(" ");while(h=c[k++]){r=h,q=null,l=h.indexOf(".")<0,m=[],l||(m=h.split("."),h=m.shift(),n=new RegExp("(^|\\.)"+d.map(m.slice(0).sort(),w).join("\\.(?:.*\\.)?")+"(\\.|$)")),p=t[h];if(!p)continue;if(!e){for(j=0;j=0&&(a.type=f=f.slice(0,-1),a.exclusive=!0),e||(a.stopPropagation(),d.event.global[f]&&d.each(d.cache,function(){var b=d.expando,e=this[b];e&&e.events&&e.events[f]&&d.event.trigger(a,c,e.handle.elem)}));if(!e||e.nodeType===3||e.nodeType===8)return b;a.result=b,a.target=e,c=d.makeArray(c),c.unshift(a)}a.currentTarget=e;var h=d._data(e,"handle");h&&h.apply(e,c);var i=e.parentNode||e.ownerDocument;try{e&&e.nodeName&&d.noData[e.nodeName.toLowerCase()]||e["on"+f]&&e["on"+f].apply(e,c)===!1&&(a.result=!1,a.preventDefault())}catch(j){}if(!a.isPropagationStopped()&&i)d.event.trigger(a,c,i,!0);else if(!a.isDefaultPrevented()){var k,l=a.target,m=f.replace(r,""),n=d.nodeName(l,"a")&&m==="click",o=d.event.special[m]||{};if((!o._default||o._default.call(e,a)===!1)&&!n&&!(l&&l.nodeName&&d.noData[l.nodeName.toLowerCase()])){try{l[m]&&(k=l["on"+m],k&&(l["on"+m]=null),d.event.triggered=a.type,l[m]())}catch(p){}k&&(l["on"+m]=k),d.event.triggered=b}}},handle:function(c){var e,f,g,h,i,j=[],k=d.makeArray(arguments);c=k[0]=d.event.fix(c||a.event),c.currentTarget=this,e=c.type.indexOf(".")<0&&!c.exclusive,e||(g=c.type.split("."),c.type=g.shift(),j=g.slice(0).sort(),h=new RegExp("(^|\\.)"+j.join("\\.(?:.*\\.)?")+"(\\.|$)")),c.namespace=c.namespace||j.join("."),i=d._data(this,"events"),f=(i||{})[c.type];if(i&&f){f=f.slice(0);for(var l=0,m=f.length;l-1?d.map(a.options,function(a){return a.selected}).join("-"):"":a.nodeName.toLowerCase()==="select"&&(c=a.selectedIndex);return c},D=function D(a){var c=a.target,e,f;if(s.test(c.nodeName)&&!c.readOnly){e=d._data(c,"_change_data"),f=C(c),(a.type!=="focusout"||c.type!=="radio")&&d._data(c,"_change_data",f);if(e===b||f===e)return;if(e!=null||f)a.type="change",a.liveFired=b,d.event.trigger(a,arguments[1],c)}};d.event.special.change={filters:{focusout:D,beforedeactivate:D,click:function(a){var b=a.target,c=b.type;(c==="radio"||c==="checkbox"||b.nodeName.toLowerCase()==="select")&&D.call(this,a)},keydown:function(a){var b=a.target,c=b.type;(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(c==="checkbox"||c==="radio")||c==="select-multiple")&&D.call(this,a)},beforeactivate:function(a){var b=a.target;d._data(b,"_change_data",C(b))}},setup:function(a,b){if(this.type==="file")return!1;for(var c in B)d.event.add(this,c+".specialChange",B[c]);return s.test(this.nodeName)},teardown:function(a){d.event.remove(this,".specialChange");return s.test(this.nodeName)}},B=d.event.special.change.filters,B.focus=B.beforeactivate}c.addEventListener&&d.each({focus:"focusin",blur:"focusout"},function(a,b){function f(a){var c=d.event.fix(a);c.type=b,c.originalEvent={},d.event.trigger(c,null,c.target),c.isDefaultPrevented()&&a.preventDefault()}var e=0;d.event.special[b]={setup:function(){e++===0&&c.addEventListener(a,f,!0)},teardown:function(){--e===0&&c.removeEventListener(a,f,!0)}}}),d.each(["bind","one"],function(a,c){d.fn[c]=function(a,e,f){if(typeof a==="object"){for(var g in a)this[c](g,e,a[g],f);return this}if(d.isFunction(e)||e===!1)f=e,e=b;var h=c==="one"?d.proxy(f,function(a){d(this).unbind(a,h);return f.apply(this,arguments)}):f;if(a==="unload"&&c!=="one")this.one(a,e,f);else for(var i=0,j=this.length;i0?this.bind(b,a,c):this.trigger(b)},d.attrFn&&(d.attrFn[b]=!0)}),function(){function u(a,b,c,d,e,f){for(var g=0,h=d.length;g0){j=i;break}}i=i[a]}d[g]=j}}}function t(a,b,c,d,e,f){for(var g=0,h=d.length;g+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,g=!1,h=!0,i=/\\/g,j=/\W/;[0,0].sort(function(){h=!1;return 0});var k=function(b,d,e,g){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!=="string")return e;var i,j,n,o,q,r,s,t,u=!0,w=k.isXML(d),x=[],y=b;do{a.exec(""),i=a.exec(y);if(i){y=i[3],x.push(i[1]);if(i[2]){o=i[3];break}}}while(i);if(x.length>1&&m.exec(b))if(x.length===2&&l.relative[x[0]])j=v(x[0]+x[1],d);else{j=l.relative[x[0]]?[d]:k(x.shift(),d);while(x.length)b=x.shift(),l.relative[b]&&(b+=x.shift()),j=v(b,j)}else{!g&&x.length>1&&d.nodeType===9&&!w&&l.match.ID.test(x[0])&&!l.match.ID.test(x[x.length-1])&&(q=k.find(x.shift(),d,w),d=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]);if(d){q=g?{expr:x.pop(),set:p(g)}:k.find(x.pop(),x.length===1&&(x[0]==="~"||x[0]==="+")&&d.parentNode?d.parentNode:d,w),j=q.expr?k.filter(q.expr,q.set):q.set,x.length>0?n=p(j):u=!1;while(x.length)r=x.pop(),s=r,l.relative[r]?s=x.pop():r="",s==null&&(s=d),l.relative[r](n,s,w)}else n=x=[]}n||(n=j),n||k.error(r||b);if(f.call(n)==="[object Array]")if(u)if(d&&d.nodeType===1)for(t=0;n[t]!=null;t++)n[t]&&(n[t]===!0||n[t].nodeType===1&&k.contains(d,n[t]))&&e.push(j[t]);else for(t=0;n[t]!=null;t++)n[t]&&n[t].nodeType===1&&e.push(j[t]);else e.push.apply(e,n);else p(n,e);o&&(k(o,h,e,g),k.uniqueSort(e));return e};k.uniqueSort=function(a){if(r){g=h,a.sort(r);if(g)for(var b=1;b0},k.find=function(a,b,c){var d;if(!a)return[];for(var e=0,f=l.order.length;e":function(a,b){var c,d=typeof b==="string",e=0,f=a.length;if(d&&!j.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(i,"")},TAG:function(a,b){return a[1].replace(i,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||k.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&k.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(i,"");!f&&l.attrMap[g]&&(a[1]=l.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(i,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=k(b[3],null,null,c);else{var g=k.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(l.match.POS.test(b[0])||l.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!k(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return"text"===c&&(b===c||b===null)},radio:function(a){return"radio"===a.type},checkbox:function(a){return"checkbox"===a.type},file:function(a){return"file"===a.type},password:function(a){return"password"===a.type},submit:function(a){return"submit"===a.type},image:function(a){return"image"===a.type},reset:function(a){return"reset"===a.type},button:function(a){return"button"===a.type||a.nodeName.toLowerCase()==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=l.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||k.getText([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=l.attrHandle[c]?l.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=l.setFilters[e];if(f)return f(a,c,b,d)}}},m=l.match.POS,n=function(a,b){return"\\"+(b-0+1)};for(var o in l.match)l.match[o]=new RegExp(l.match[o].source+/(?![^\[]*\])(?![^\(]*\))/.source),l.leftMatch[o]=new RegExp(/(^(?:.|\r|\n)*?)/.source+l.match[o].source.replace(/\\(\d+)/g,n));var p=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(q){p=function(a,b){var c=0,d=b||[];if(f.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length==="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(l.find.ID=function(a,c,d){if(typeof c.getElementById!=="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!=="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},l.filter.ID=function(a,b){var c=typeof a.getAttributeNode!=="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(l.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!=="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(l.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=k,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){k=function(b,e,f,g){e=e||c;if(!g&&!k.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return p(e.getElementsByTagName(b),f);if(h[2]&&l.find.CLASS&&e.getElementsByClassName)return p(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return p([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return p([],f);if(i.id===h[3])return p([i],f)}try{return p(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var m=e,n=e.getAttribute("id"),o=n||d,q=e.parentNode,r=/^\s*[+~]/.test(b);n?o=o.replace(/'/g,"\\$&"):e.setAttribute("id",o),r&&q&&(e=e.parentNode);try{if(!r||q)return p(e.querySelectorAll("[id='"+o+"'] "+b),f)}catch(s){}finally{n||m.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)k[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}k.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(a))try{if(e||!l.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return k(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
";if(a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;l.order.splice(1,0,"CLASS"),l.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!=="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?k.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?k.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:k.contains=function(){return!1},k.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var v=function(a,b){var c,d=[],e="",f=b.nodeType?[b]:b;while(c=l.match.PSEUDO.exec(a))e+=c[0],a=a.replace(l.match.PSEUDO,"");a=l.relative[a]?a+"*":a;for(var g=0,h=f.length;g0)for(var g=c;g0},closest:function(a,b){var c=[],e,f,g=this[0];if(d.isArray(a)){var h,i,j={},k=1;if(g&&a.length){for(e=0,f=a.length;e-1:d(g).is(h))&&c.push({selector:i,elem:g,level:k});g=g.parentNode,k++}}return c}var l=N.test(a)?d(a,b||this.context):null;for(e=0,f=this.length;e-1:d.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b)break}}c=c.length>1?d.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a||typeof a==="string")return d.inArray(this[0],a?d(a):this.parent().children());return d.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a==="string"?d(a,b):d.makeArray(a),e=d.merge(this.get(),c);return this.pushStack(P(c[0])||P(e[0])?e:d.unique(e))},andSelf:function(){return this.add(this.prevObject)}}),d.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return d.dir(a,"parentNode")},parentsUntil:function(a,b,c){return d.dir(a,"parentNode",c)},next:function(a){return d.nth(a,2,"nextSibling")},prev:function(a){return d.nth(a,2,"previousSibling")},nextAll:function(a){return d.dir(a,"nextSibling")},prevAll:function(a){return d.dir(a,"previousSibling")},nextUntil:function(a,b,c){return d.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return d.dir(a,"previousSibling",c)},siblings:function(a){return d.sibling(a.parentNode.firstChild,a)},children:function(a){return d.sibling(a.firstChild)},contents:function(a){return d.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:d.makeArray(a.childNodes)}},function(a,b){d.fn[a]=function(c,e){var f=d.map(this,b,c),g=M.call(arguments);I.test(a)||(e=c),e&&typeof e==="string"&&(f=d.filter(e,f)),f=this.length>1&&!O[a]?d.unique(f):f,(this.length>1||K.test(e))&&J.test(a)&&(f=f.reverse());return this.pushStack(f,a,g.join(","))}}),d.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?d.find.matchesSelector(b[0],a)?[b[0]]:[]:d.find.matches(a,b)},dir:function(a,c,e){var f=[],g=a[c];while(g&&g.nodeType!==9&&(e===b||g.nodeType!==1||!d(g).is(e)))g.nodeType===1&&f.push(g),g=g[c];return f},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var R=/ jQuery\d+="(?:\d+|null)"/g,S=/^\s+/,T=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,U=/<([\w:]+)/,V=/",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]};Z.optgroup=Z.option,Z.tbody=Z.tfoot=Z.colgroup=Z.caption=Z.thead,Z.th=Z.td,d.support.htmlSerialize||(Z._default=[1,"div
","
"]),d.fn.extend({text:function(a){if(d.isFunction(a))return this.each(function(b){var c=d(this);c.text(a.call(this,b,c.text()))});if(typeof a!=="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return d.text(this)},wrapAll:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapAll(a.call(this,b))});if(this[0]){var b=d(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(d.isFunction(a))return this.each(function(b){d(this).wrapInner(a.call(this,b))});return this.each(function(){var b=d(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){d(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){d.nodeName(this,"body")||d(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=d(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,d(arguments[0]).toArray());return a}},remove:function(a,b){for(var c=0,e;(e=this[c])!=null;c++)if(!a||d.filter(a,[e]).length)!b&&e.nodeType===1&&(d.cleanData(e.getElementsByTagName("*")),d.cleanData([e])),e.parentNode&&e.parentNode.removeChild(e);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&d.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return d.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(R,""):null;if(typeof a!=="string"||X.test(a)||!d.support.leadingWhitespace&&S.test(a)||Z[(U.exec(a)||["",""])[1].toLowerCase()])d.isFunction(a)?this.each(function(b){var c=d(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);else{a=a.replace(T,"<$1>");try{for(var c=0,e=this.length;c1&&l0?this.clone(!0):this).get();d(f[h])[b](j),e=e.concat(j)}return this.pushStack(e,a,f.selector)}}),d.extend({clone:function(a,b,c){var e=a.cloneNode(!0),f,g,h;if((!d.support.noCloneEvent||!d.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!d.isXMLDoc(a)){ba(a,e),f=bb(a),g=bb(e);for(h=0;f[h];++h)ba(f[h],g[h])}if(b){_(a,e);if(c){f=bb(a),g=bb(e);for(h=0;f[h];++h)_(f[h],g[h])}}return e},clean:function(a,b,e,f){b=b||c,typeof b.createElement==="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var g=[];for(var h=0,i;(i=a[h])!=null;h++){typeof i==="number"&&(i+="");if(!i)continue;if(typeof i!=="string"||W.test(i)){if(typeof i==="string"){i=i.replace(T,"<$1>");var j=(U.exec(i)||["",""])[1].toLowerCase(),k=Z[j]||Z._default,l=k[0],m=b.createElement("div");m.innerHTML=k[1]+i+k[2];while(l--)m=m.lastChild;if(!d.support.tbody){var n=V.test(i),o=j==="table"&&!n?m.firstChild&&m.firstChild.childNodes:k[1]===""&&!n?m.childNodes:[];for(var p=o.length-1;p>=0;--p)d.nodeName(o[p],"tbody")&&!o[p].childNodes.length&&o[p].parentNode.removeChild(o[p])}!d.support.leadingWhitespace&&S.test(i)&&m.insertBefore(b.createTextNode(S.exec(i)[0]),m.firstChild),i=m.childNodes}}else i=b.createTextNode(i);i.nodeType?g.push(i):g=d.merge(g,i)}if(e)for(h=0;g[h];h++)!f||!d.nodeName(g[h],"script")||g[h].type&&g[h].type.toLowerCase()!=="text/javascript"?(g[h].nodeType===1&&g.splice.apply(g,[h+1,0].concat(d.makeArray(g[h].getElementsByTagName("script")))),e.appendChild(g[h])):f.push(g[h].parentNode?g[h].parentNode.removeChild(g[h]):g[h]);return g},cleanData:function(a){var b,c,e=d.cache,f=d.expando,g=d.event.special,h=d.support.deleteExpando;for(var i=0,j;(j=a[i])!=null;i++){if(j.nodeName&&d.noData[j.nodeName.toLowerCase()])continue;c=j[d.expando];if(c){b=e[c]&&e[c][f];if(b&&b.events){for(var k in b.events)g[k]?d.event.remove(j,k):d.removeEvent(j,k,b.handle);b.handle&&(b.handle.elem=null)}h?delete j[d.expando]:j.removeAttribute&&j.removeAttribute(d.expando),delete e[c]}}}});var bd=/alpha\([^)]*\)/i,be=/opacity=([^)]*)/,bf=/-([a-z])/ig,bg=/([A-Z]|^ms)/g,bh=/^-?\d+(?:px)?$/i,bi=/^-?\d/,bj={position:"absolute",visibility:"hidden",display:"block"},bk=["Left","Right"],bl=["Top","Bottom"],bm,bn,bo,bp=function(a,b){return b.toUpperCase()};d.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return d.access(this,a,c,!0,function(a,c,e){return e!==b?d.style(a,c,e):d.css(a,c)})},d.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bm(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{zIndex:!0,fontWeight:!0,opacity:!0,zoom:!0,lineHeight:!0},cssProps:{"float":d.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,e,f){if(a&&a.nodeType!==3&&a.nodeType!==8&&a.style){var g,h=d.camelCase(c),i=a.style,j=d.cssHooks[h];c=d.cssProps[h]||h;if(e===b){if(j&&"get"in j&&(g=j.get(a,!1,f))!==b)return g;return i[c]}if(typeof e==="number"&&isNaN(e)||e==null)return;typeof e==="number"&&!d.cssNumber[h]&&(e+="px");if(!j||!("set"in j)||(e=j.set(a,e))!==b)try{i[c]=e}catch(k){}}},css:function(a,c,e){var f,g=d.camelCase(c),h=d.cssHooks[g];c=d.cssProps[g]||g;if(h&&"get"in h&&(f=h.get(a,!0,e))!==b)return f;if(bm)return bm(a,c,g)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]},camelCase:function(a){return a.replace(bf,bp)}}),d.curCSS=d.css,d.each(["height","width"],function(a,b){d.cssHooks[b]={get:function(a,c,e){var f;if(c){a.offsetWidth!==0?f=bq(a,b,e):d.swap(a,bj,function(){f=bq(a,b,e)});if(f<=0){f=bm(a,b,b),f==="0px"&&bo&&(f=bo(a,b,b));if(f!=null)return f===""||f==="auto"?"0px":f}if(f<0||f==null){f=a.style[b];return f===""||f==="auto"?"0px":f}return typeof f==="string"?f:f+"px"}},set:function(a,b){if(!bh.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),d.support.opacity||(d.cssHooks.opacity={get:function(a,b){return be.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style;c.zoom=1;var e=d.isNaN(b)?"":"alpha(opacity="+b*100+")",f=c.filter||"";c.filter=bd.test(f)?f.replace(bd,e):c.filter+" "+e}}),d(function(){d.support.reliableMarginRight||(d.cssHooks.marginRight={get:function(a,b){var c;d.swap(a,{display:"inline-block"},function(){b?c=bm(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bn=function(a,c,e){var f,g,h;e=e.replace(bg,"-$1").toLowerCase();if(!(g=a.ownerDocument.defaultView))return b;if(h=g.getComputedStyle(a,null))f=h.getPropertyValue(e),f===""&&!d.contains(a.ownerDocument.documentElement,a)&&(f=d.style(a,e));return f}),c.documentElement.currentStyle&&(bo=function(a,b){var c,d=a.currentStyle&&a.currentStyle[b],e=a.runtimeStyle&&a.runtimeStyle[b],f=a.style;!bh.test(d)&&bi.test(d)&&(c=f.left,e&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":d||0,d=f.pixelLeft+"px",f.left=c,e&&(a.runtimeStyle.left=e));return d===""?"auto":d}),bm=bn||bo,d.expr&&d.expr.filters&&(d.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!d.support.reliableHiddenOffsets&&(a.style.display||d.css(a,"display"))==="none"},d.expr.filters.visible=function(a){return!d.expr.filters.hidden(a)});var br=/%20/g,bs=/\[\]$/,bt=/\r?\n/g,bu=/#.*$/,bv=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bw=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bx=/^(?:about|app|app\-storage|.+\-extension|file|widget):$/,by=/^(?:GET|HEAD)$/,bz=/^\/\//,bA=/\?/,bB=/)<[^<]*)*<\/script>/gi,bC=/^(?:select|textarea)/i,bD=/\s+/,bE=/([?&])_=[^&]*/,bF=/(^|\-)([a-z])/g,bG=function(a,b,c){return b+c.toUpperCase()},bH=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bI=d.fn.load,bJ={},bK={},bL,bM;try{bL=c.location.href}catch(bN){bL=c.createElement("a"),bL.href="",bL=bL.href}bM=bH.exec(bL.toLowerCase())||[],d.fn.extend({load:function(a,c,e){if(typeof a!=="string"&&bI)return bI.apply(this,arguments);if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var g=a.slice(f,a.length);a=a.slice(0,f)}var h="GET";c&&(d.isFunction(c)?(e=c,c=b):typeof c==="object"&&(c=d.param(c,d.ajaxSettings.traditional),h="POST"));var i=this;d.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?d("
").append(c.replace(bB,"")).find(g):c)),e&&i.each(e,[c,b,a])}});return this},serialize:function(){return d.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?d.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bC.test(this.nodeName)||bw.test(this.type))}).map(function(a,b){var c=d(this).val();return c==null?null:d.isArray(c)?d.map(c,function(a,c){return{name:b.name,value:a.replace(bt,"\r\n")}}):{name:b.name,value:c.replace(bt,"\r\n")}}).get()}}),d.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){d.fn[b]=function(a){return this.bind(b,a)}}),d.each(["get","post"],function(a,c){d[c]=function(a,e,f,g){d.isFunction(e)&&(g=g||f,f=e,e=b);return d.ajax({type:c,url:a,data:e,success:f,dataType:g})}}),d.extend({getScript:function(a,c){return d.get(a,b,c,"script")},getJSON:function(a,b,c){return d.get(a,b,c,"json")},ajaxSetup:function(a,b){b?d.extend(!0,a,d.ajaxSettings,b):(b=a,a=d.extend(!0,d.ajaxSettings,b));for(var c in {context:1,url:1})c in b?a[c]=b[c]:c in d.ajaxSettings&&(a[c]=d.ajaxSettings[c]);return a},ajaxSettings:{url:bL,isLocal:bx.test(bM[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":"*/*"},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":d.parseJSON,"text xml":d.parseXML}},ajaxPrefilter:bO(bJ),ajaxTransport:bO(bK),ajax:function(a,c){function v(a,c,l,n){if(r!==2){r=2,p&&clearTimeout(p),o=b,m=n||"",u.readyState=a?4:0;var q,t,v,w=l?bR(e,u,l):b,x,y;if(a>=200&&a<300||a===304){if(e.ifModified){if(x=u.getResponseHeader("Last-Modified"))d.lastModified[k]=x;if(y=u.getResponseHeader("Etag"))d.etag[k]=y}if(a===304)c="notmodified",q=!0;else try{t=bS(e,w),c="success",q=!0}catch(z){c="parsererror",v=z}}else{v=c;if(!c||a)c="error",a<0&&(a=0)}u.status=a,u.statusText=c,q?h.resolveWith(f,[t,c,u]):h.rejectWith(f,[u,c,v]),u.statusCode(j),j=b,s&&g.trigger("ajax"+(q?"Success":"Error"),[u,e,q?t:v]),i.resolveWith(f,[u,c]),s&&(g.trigger("ajaxComplete",[u,e]),--d.active||d.event.trigger("ajaxStop"))}}typeof a==="object"&&(c=a,a=b),c=c||{};var e=d.ajaxSetup({},c),f=e.context||e,g=f!==e&&(f.nodeType||f instanceof d)?d(f):d.event,h=d.Deferred(),i=d._Deferred(),j=e.statusCode||{},k,l={},m,n,o,p,q,r=0,s,t,u={readyState:0,setRequestHeader:function(a,b){r||(l[a.toLowerCase().replace(bF,bG)]=b);return this},getAllResponseHeaders:function(){return r===2?m:null},getResponseHeader:function(a){var c;if(r===2){if(!n){n={};while(c=bv.exec(m))n[c[1].toLowerCase()]=c[2]}c=n[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){r||(e.mimeType=a);return this},abort:function(a){a=a||"abort",o&&o.abort(a),v(0,a);return this}};h.promise(u),u.success=u.done,u.error=u.fail,u.complete=i.done,u.statusCode=function(a){if(a){var b;if(r<2)for(b in a)j[b]=[j[b],a[b]];else b=a[u.status],u.then(b,b)}return this},e.url=((a||e.url)+"").replace(bu,"").replace(bz,bM[1]+"//"),e.dataTypes=d.trim(e.dataType||"*").toLowerCase().split(bD),e.crossDomain==null&&(q=bH.exec(e.url.toLowerCase()),e.crossDomain=q&&(q[1]!=bM[1]||q[2]!=bM[2]||(q[3]||(q[1]==="http:"?80:443))!=(bM[3]||(bM[1]==="http:"?80:443)))),e.data&&e.processData&&typeof e.data!=="string"&&(e.data=d.param(e.data,e.traditional)),bP(bJ,e,c,u);if(r===2)return!1;s=e.global,e.type=e.type.toUpperCase(),e.hasContent=!by.test(e.type),s&&d.active++===0&&d.event.trigger("ajaxStart");if(!e.hasContent){e.data&&(e.url+=(bA.test(e.url)?"&":"?")+e.data),k=e.url;if(e.cache===!1){var w=d.now(),x=e.url.replace(bE,"$1_="+w);e.url=x+(x===e.url?(bA.test(e.url)?"&":"?")+"_="+w:"")}}if(e.data&&e.hasContent&&e.contentType!==!1||c.contentType)l["Content-Type"]=e.contentType;e.ifModified&&(k=k||e.url,d.lastModified[k]&&(l["If-Modified-Since"]=d.lastModified[k]),d.etag[k]&&(l["If-None-Match"]=d.etag[k])),l.Accept=e.dataTypes[0]&&e.accepts[e.dataTypes[0]]?e.accepts[e.dataTypes[0]]+(e.dataTypes[0]!=="*"?", */*; q=0.01":""):e.accepts["*"];for(t in e.headers)u.setRequestHeader(t,e.headers[t]);if(e.beforeSend&&(e.beforeSend.call(f,u,e)===!1||r===2)){u.abort();return!1}for(t in {success:1,error:1,complete:1})u[t](e[t]);o=bP(bK,e,c,u);if(o){u.readyState=1,s&&g.trigger("ajaxSend",[u,e]),e.async&&e.timeout>0&&(p=setTimeout(function(){u.abort("timeout")},e.timeout));try{r=1,o.send(l,v)}catch(y){status<2?v(-1,y):d.error(y)}}else v(-1,"No Transport");return u},param:function(a,c){var e=[],f=function(a,b){b=d.isFunction(b)?b():b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=d.ajaxSettings.traditional);if(d.isArray(a)||a.jquery&&!d.isPlainObject(a))d.each(a,function(){f(this.name,this.value)});else for(var g in a)bQ(g,a[g],c,f);return e.join("&").replace(br,"+")}}),d.extend({active:0,lastModified:{},etag:{}});var bT=d.now(),bU=/(\=)\?(&|$)|\?\?/i;d.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return d.expando+"_"+bT++}}),d.ajaxPrefilter("json jsonp",function(b,c,e){var f=typeof b.data==="string";if(b.dataTypes[0]==="jsonp"||c.jsonpCallback||c.jsonp!=null||b.jsonp!==!1&&(bU.test(b.url)||f&&bU.test(b.data))){var g,h=b.jsonpCallback=d.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2",m=function(){a[h]=i,g&&d.isFunction(i)&&a[h](g[0])};b.jsonp!==!1&&(j=j.replace(bU,l),b.url===j&&(f&&(k=k.replace(bU,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},e.then(m,m),b.converters["script json"]=function(){g||d.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),d.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){d.globalEval(a);return a}}}),d.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),d.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var bV=d.now(),bW,bX;d.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&bZ()||b$()}:bZ,bX=d.ajaxSettings.xhr(),d.support.ajax=!!bX,d.support.cors=bX&&"withCredentials"in bX,bX=b,d.support.ajax&&d.ajaxTransport(function(a){if(!a.crossDomain||d.support.cors){var c;return{send:function(e,f){var g=a.xhr(),h,i;a.username?g.open(a.type,a.url,a.async,a.username,a.password):g.open(a.type,a.url,a.async);if(a.xhrFields)for(i in a.xhrFields)g[i]=a.xhrFields[i];a.mimeType&&g.overrideMimeType&&g.overrideMimeType(a.mimeType),!a.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(i in e)g.setRequestHeader(i,e[i])}catch(j){}g.send(a.hasContent&&a.data||null),c=function(e,i){var j,k,l,m,n;try{if(c&&(i||g.readyState===4)){c=b,h&&(g.onreadystatechange=d.noop,delete bW[h]);if(i)g.readyState!==4&&g.abort();else{j=g.status,l=g.getAllResponseHeaders(),m={},n=g.responseXML,n&&n.documentElement&&(m.xml=n),m.text=g.responseText;try{k=g.statusText}catch(o){k=""}j||!a.isLocal||a.crossDomain?j===1223&&(j=204):j=m.text?200:404}}}catch(p){i||f(-1,p)}m&&f(j,k,m,l)},a.async&&g.readyState!==4?(bW||(bW={},bY()),h=bV++,g.onreadystatechange=bW[h]=c):c()},abort:function(){c&&c(0,1)}}}});var b_={},ca=/^(?:toggle|show|hide)$/,cb=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cc,cd=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];d.fn.extend({show:function(a,b,c){var e,f;if(a||a===0)return this.animate(ce("show",3),a,b,c);for(var g=0,h=this.length;g=0;a--)c[a].elem===this&&(b&&c[a](!0),c.splice(a,1))}),b||this.dequeue();return this}}),d.each({slideDown:ce("show",1),slideUp:ce("hide",1),slideToggle:ce("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){d.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),d.extend({speed:function(a,b,c){var e=a&&typeof a==="object"?d.extend({},a):{complete:c||!c&&b||d.isFunction(a)&&a,duration:a,easing:c&&b||b&&!d.isFunction(b)&&b};e.duration=d.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in d.fx.speeds?d.fx.speeds[e.duration]:d.fx.speeds._default,e.old=e.complete,e.complete=function(){e.queue!==!1&&d(this).dequeue(),d.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig||(b.orig={})}}),d.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(d.fx.step[this.prop]||d.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=d.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,b,c){function g(a){return e.step(a)}var e=this,f=d.fx;this.startTime=d.now(),this.start=a,this.end=b,this.unit=c||this.unit||(d.cssNumber[this.prop]?"":"px"),this.now=this.start,this.pos=this.state=0,g.elem=this.elem,g()&&d.timers.push(g)&&!cc&&(cc=setInterval(f.tick,f.interval))},show:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.show=!0,this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),d(this.elem).show()},hide:function(){this.options.orig[this.prop]=d.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b=d.now(),c=!0;if(a||b>=this.options.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),this.options.curAnim[this.prop]=!0;for(var e in this.options.curAnim)this.options.curAnim[e]!==!0&&(c=!1);if(c){if(this.options.overflow!=null&&!d.support.shrinkWrapBlocks){var f=this.elem,g=this.options;d.each(["","X","Y"],function(a,b){f.style["overflow"+b]=g.overflow[a]})}this.options.hide&&d(this.elem).hide();if(this.options.hide||this.options.show)for(var h in this.options.curAnim)d.style(this.elem,h,this.options.orig[h]);this.options.complete.call(this.elem)}return!1}var i=b-this.startTime;this.state=i/this.options.duration;var j=this.options.specialEasing&&this.options.specialEasing[this.prop],k=this.options.easing||(d.easing.swing?"swing":"linear");this.pos=d.easing[j||k](this.state,i,0,1,this.options.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update();return!0}},d.extend(d.fx,{tick:function(){var a=d.timers;for(var b=0;b
";d.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"}),b.innerHTML=j,a.insertBefore(b,a.firstChild),e=b.firstChild,f=e.firstChild,h=e.nextSibling.firstChild.firstChild,this.doesNotAddBorder=f.offsetTop!==5,this.doesAddBorderForTableAndCells=h.offsetTop===5,f.style.position="fixed",f.style.top="20px",this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15,f.style.position=f.style.top="",e.style.overflow="hidden",e.style.position="relative",this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5,this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==i,a.removeChild(b),d.offset.initialize=d.noop},bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;d.offset.initialize(),d.offset.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(d.css(a,"marginTop"))||0,c+=parseFloat(d.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var e=d.css(a,"position");e==="static"&&(a.style.position="relative");var f=d(a),g=f.offset(),h=d.css(a,"top"),i=d.css(a,"left"),j=(e==="absolute"||e==="fixed")&&d.inArray("auto",[h,i])>-1,k={},l={},m,n;j&&(l=f.position()),m=j?l.top:parseInt(h,10)||0,n=j?l.left:parseInt(i,10)||0,d.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):f.css(k)}},d.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),e=ch.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(d.css(a,"marginTop"))||0,c.left-=parseFloat(d.css(a,"marginLeft"))||0,e.top+=parseFloat(d.css(b[0],"borderTopWidth"))||0,e.left+=parseFloat(d.css(b[0],"borderLeftWidth"))||0;return{top:c.top-e.top,left:c.left-e.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&(!ch.test(a.nodeName)&&d.css(a,"position")==="static"))a=a.offsetParent;return a})}}),d.each(["Left","Top"],function(a,c){var e="scroll"+c;d.fn[e]=function(c){var f=this[0],g;if(!f)return null;if(c!==b)return this.each(function(){g=ci(this),g?g.scrollTo(a?d(g).scrollLeft():c,a?c:d(g).scrollTop()):this[e]=c});g=ci(f);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:d.support.boxModel&&g.document.documentElement[e]||g.document.body[e]:f[e]}}),d.each(["Height","Width"],function(a,c){var e=c.toLowerCase();d.fn["inner"+c]=function(){return this[0]?parseFloat(d.css(this[0],e,"padding")):null},d.fn["outer"+c]=function(a){return this[0]?parseFloat(d.css(this[0],e,a?"margin":"border")):null},d.fn[e]=function(a){var f=this[0];if(!f)return a==null?null:this;if(d.isFunction(a))return this.each(function(b){var c=d(this);c[e](a.call(this,b,c[e]()))});if(d.isWindow(f)){var g=f.document.documentElement["client"+c];return f.document.compatMode==="CSS1Compat"&&g||f.document.body["client"+c]||g}if(f.nodeType===9)return Math.max(f.documentElement["client"+c],f.body["scroll"+c],f.documentElement["scroll"+c],f.body["offset"+c],f.documentElement["offset"+c]);if(a===b){var h=d.css(f,e),i=parseFloat(h);return d.isNaN(i)?h:i}return this.css(e,typeof a==="string"?a:a+"px")}}),a.jQuery=a.$=d})(window); \ No newline at end of file diff --git a/freesound/static/bw-frontend/public/embeds/maps-mapbox.js b/freesound/static/bw-frontend/public/embeds/maps-mapbox.js new file mode 100644 index 000000000..e596d9e88 --- /dev/null +++ b/freesound/static/bw-frontend/public/embeds/maps-mapbox.js @@ -0,0 +1,441 @@ +var FREESOUND_SATELLITE_STYLE_ID = 'cjgxefqkb00142roas6kmqneq'; +var FREESOUND_STREETS_STYLE_ID = 'cjkmk0h7p79z32spe9j735hrd'; +var MIN_INPUT_CHARACTERS_FOR_GEOCODER = 3; // From mapbox docs: "Minimum number of characters to enter before [geocoder] results are shown" + +function setMaxZoomCenter(lat, lng, zoom) { + window.map.flyTo({'center': [lng, lat], 'zoom': zoom - 1}); // Subtract 1 for compatibility with gmaps zoom levels +} + +function getSoundsLocations(url, callback){ + /* + Loads geotag data from Freesound endpoint and returns as list of tuples with: + [(sound_id, sound_latitude, sound_longitude), ...] + */ + var resp = []; + var oReq = new XMLHttpRequest(); + oReq.open("GET", url, true); + oReq.responseType = "arraybuffer"; + oReq.onload = function(oEvent) { + var raw_data = new Int32Array(oReq.response); + + var id = null; + var lat = null; + var lon = null; + + for (var i = 0; i < raw_data.length; i += 3) { + id = raw_data[i]; + lat = raw_data[i+1] / 1000000; + lon = raw_data[i+2] / 1000000; + resp.push([id, lat, lon]); + } + callback(resp); + }; + oReq.send(); +} + +function call_on_bounds_chage_callback(map, map_element_id, callback){ + /* Util function used in "make_sounds_map" and "make_geotag_edit_map" to get parameters to cll callback */ + callback( // The callback is called with the following arguments: + map_element_id, // ID of the element containing the map + map.getCenter().lat, // Latitude (at map center) + map.getCenter().lng, // Longitude (at map center) + map.getZoom() + 1, // Add 1 for compatibility with gmaps zoom levels + map.getBounds().getSouthWest().lat, // Latidude (at bottom left of map) + map.getBounds().getSouthWest().lng, // Longitude (at bottom left of map) + map.getBounds().getNorthEast().lat, // Latidude (at top right of map) + map.getBounds().getNorthEast().lng // Longitude (at top right of map) + ) +} + +function make_sounds_map(geotags_url, map_element_id, on_built_callback, on_bounds_changed_callback, + center_lat, center_lon, zoom, show_search, show_style_selector, cluster){ + /* + This function is used to display maps with sounds. It is used in all pages where maps with markers (which represent + sounds) are shown: user home/profile, pack page, geotags map, embeds. Parameters: + + - geotags_url: Freesound URL that returns the list of geotags that will be shown in the map. + - map_element_id: DOM element id where the map will be placed. + - on_built_callback: function to be called once the map has been built (takes no parameters) + - on_bounds_changed_callback: function called when the bounds of the map change (because of user interaction). As + parameters it gets the map ID, updated center latitude, center longitude and zoom, as well as bottom left + corner latitude and longitude, and top right corner latitude and longitude (see code below for more + specific details). This callback is also called after map is loaded (bounds are set for the first time). + - center_lat: latitude where to center the map (if not specified, it is automatically determined based on markers) + - center_lon: latitude where to center the map (if not specified, it is automatically determined based on markers) + - zoom: initial zoom for the map (if not specified, it is automatically determined based on markers) + - show_search: display search bar to fly to places in the map + - show_terrain_selector: display button to switch between streets and satellite styles + - cluster: whether or not to perform point clustering (on by default) + + This function first calls the Freesound endpoint which returns the list of geotags to be displayed as markers. + Once the data is received, it creates the map and does all necessary stuff to display it. + + NOTE: the map created using this function is assigned to window.map to make it accessible from everywhere. This is + needed for the "zoom in" method in info windows. If more than one map is shown in a single page (which does not + happen in Freesound) the "zoom in" method in inforwindows won't work properly. + */ + + getSoundsLocations(geotags_url, function(data){ + var nSounds = data.length; + if (nSounds >= 0) { + + // Define initial map center and zoom + var init_zoom = 2; + var init_lat = 22; + var init_lon= 24; + if ((center_lat !== undefined) && (center_lon !== undefined) && (zoom !== undefined)){ + // If center and zoom properties are given, use them to init the map + init_zoom = zoom; + init_lat = center_lat; + init_lon = center_lon; + } + + // Init map and info window objects + var map = new mapboxgl.Map({ + container: map_element_id, // HTML container id + style: 'mapbox://styles/freesound/' + FREESOUND_SATELLITE_STYLE_ID, // style URL + center: [init_lon, init_lat], // starting position as [lng, lat] + zoom: init_zoom - 1, // Subtract 1 for compatibility with gmaps zoom levels + maxZoom: 18, + }); + map.dragRotate.disable(); + map.touchZoomRotate.disableRotation(); + map.addControl(new mapboxgl.NavigationControl({ showCompass: false })); + if (show_search === true){ + map.addControl(new MapboxGeocoder({ accessToken: mapboxgl.accessToken, minLength: MIN_INPUT_CHARACTERS_FOR_GEOCODER}), 'top-left'); + } + window.map = map; // Used to have a global reference to the map + + // Get coordinates for each sound + var geojson_features = []; + var bounds = new mapboxgl.LngLatBounds(); + $.each(data, function(index, item) { + var id = item[0]; + var lat = item[1]; + var lon = item[2]; + + geojson_features.push({ + "type": "Feature", + "properties": { + "id": id, + }, + "geometry": { + "type": "Point", + "coordinates": [ lon, lat ] + } + }); + bounds.extend([lon, lat]); + }); + + map.on('load', function() { + + // Add satellite/streets controls + if (show_style_selector === true) { + $('#' + map_element_id).append('
Show streets
'); + } + + // Add popups + map.on('click', 'sounds-unclustered', function (e) { + + stopAll(); + var coordinates = e.features[0].geometry.coordinates.slice(); + var sound_id = e.features[0].properties.id; + + ajaxLoad( '/geotags/infowindow/' + sound_id + '?embed=1', function(data, responseCode) + { + // Ensure that if the map is zoomed out such that multiple + // copies of the feature are visible, the popup appears + // over the copy being pointed to. + while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { + coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360; + } + var popup = new mapboxgl.Popup() + .setLngLat(coordinates) + .setHTML(data.response) + .addTo(map); + + popup.on('close', function(e) { + stopAll(); // Stop sound on popup close + }); + makePlayer('.infowindow_player .player'); + }); + }); + + // Change the cursor to a pointer when the mouse is over the places layer. + map.on('mouseenter', 'sounds-unclustered', function () { + map.getCanvas().style.cursor = 'pointer'; + }); + + // Change it back to a pointer when it leaves. + map.on('mouseleave', 'sounds-unclustered', function () { + map.getCanvas().style.cursor = ''; + }); + + // Zoom-in when clicking on clusters + map.on('click', 'sounds-clusters', function (e) { + map.flyTo({'center': e.lngLat, 'zoom': map.getZoom() + 4}); + }); + + // Adjust map boundaries + if (center_lat === undefined){ + // If initital center and zoom were not given, adjust map boundaries now based on the sounds + if (nSounds > 1){ + map.fitBounds(bounds, {duration:0, padding: {top:40, right:40, left:40, bottom:40}}); + } else { + map.setZoom(3); + map.setCenter(geojson_features[0].geometry.coordinates); + } + } + + // Run callback function (if passed) after map is built + if (on_built_callback !== undefined){ + on_built_callback(); + } + + // Add listener for callback on bounds changed + if (on_bounds_changed_callback !== undefined) { + call_on_bounds_chage_callback(map, map_element_id, on_bounds_changed_callback); + map.on('moveend', function(e) { + call_on_bounds_chage_callback(map, map_element_id, on_bounds_changed_callback); + }); + } + }); + + + map.on('style.load', function () { // Triggered when `setStyle` is called, add all data layers + map.loadImage('/static/bw-frontend/public/embeds/images/map_marker.png', function(error, image) { + map.addImage("custom-marker", image); + + // Setup clustering + if (cluster === undefined){ + cluster = true; + } + + // Add a new source from our GeoJSON data and set the + // 'cluster' option to true. GL-JS will add the point_count property to your source data. + map.addSource("sounds", { + type: "geojson", + data: { + "type": "FeatureCollection", + "features": geojson_features + }, + cluster: cluster, + clusterMaxZoom: 10, // Max zoom to cluster points on + clusterRadius: 30 // Radius of each cluster when clustering points (defaults to 50) + }); + + map.addLayer({ + id: "sounds-clusters", + type: "circle", + source: "sounds", + filter: ["has", "point_count"], + paint: { + // Use step expressions (https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-step) + // with three steps to implement three types of circles: + // * Blue, 20px circles when point count is less than 100 + // * Yellow, 30px circles when point count is between 100 and 750 + // * Pink, 40px circles when point count is greater than or equal to 750 + "circle-color": [ + "step", + ["get", "point_count"], + "#007fff", // 0 to 10 + 10, + "#ffad00", // 10 to 100 + 100, + "#ff0006", // 100 to 1000 + 1000, + "#ff00ef" // 1000+ + ], + "circle-radius": [ + "step", + ["get", "point_count"], + 12, // 0 to 10 + 10, + 14, // 10 to 100 + 100, + 16, // 100 to 1000 + 1000, + 18 // 1000+ + ] + } + }); + + map.addLayer({ + id: "sounds-cluster-labels", + type: "symbol", + source: "sounds", + filter: ["has", "point_count"], + layout: { + "text-field": "{point_count_abbreviated}", + "text-font": ["Arial Unicode MS Bold"], + "text-size": 12 + } + }); + + map.addLayer({ + id: "sounds-unclustered", + type: "symbol", + source: "sounds", + filter: ["!has", "point_count"], + layout: { + "icon-image": "custom-marker", + "icon-allow-overlap": true, + } + }); + }); + }); + } + }); +} + + +function make_geotag_edit_map(map_element_id, arrow_url, on_bounds_changed_callback, + center_lat, center_lon, zoom, idx){ + + /* + This function is used to display the map used to add a geotag to a sound maps with sounds. It is used in the sound + edit page and the sound describe page. + + - map_element_id: DOM element id where the map will be placed. + - on_bounds_changed_callback: function called when the bounds of the map change (because of user interaction). As + parameters it gets the map ID, updated center latitude, center longitude and zoom, as well as bottom left + corner latitude and longitude, and top right corner latitude and longitude (see code below for more + specific details). + - center_lat: latitude where to center the map (if not specified, it uses a default one) + - center_lon: latitude where to center the map (if not specified, it uses a default one) + - zoom: initial zoom for the map (if not specified, it uses a default one) + - idx: map idx (used when creating multiple maps) + + This function returns the object of the map that has been created. + */ + + // Initialize map + if (center_lat === undefined){ // Load default center + center_lat = 23.8858; + center_lon = 21.7968; + zoom = 1; + } + var initial_center = [parseFloat(center_lon, 10), parseFloat(center_lat, 10)]; + var map = new mapboxgl.Map({ + container: map_element_id, // HTML container id + style: 'mapbox://styles/freesound/cjgxefqkb00142roas6kmqneq', // style URL (custom style with satellite and labels) + center: initial_center, // starting position as [lng, lat] + zoom: parseInt(zoom, 10) - 1, // Subtract 1 for compatibility with gmaps zoom levels + maxZoom: 18, + maxBounds: [[-360,-90],[360,90]] + }); + map.dragRotate.disable(); + map.touchZoomRotate.disableRotation(); + map.addControl(new mapboxgl.NavigationControl({ showCompass: false })); + map.addControl(new MapboxGeocoder({ accessToken: mapboxgl.accessToken, minLength: MIN_INPUT_CHARACTERS_FOR_GEOCODER }), 'top-left'); + + map.toggleStyle = function() { + toggleMapStyle(map, map_element_id); + }; + + map.on('load', function() { + // Add controls for toggling style + $('#' + map_element_id).append('
Show streets
'); + + // Add listener for callback on bounds changed + if (on_bounds_changed_callback !== undefined) { + // Initial call to on_bounds_changed_callback + call_on_bounds_chage_callback(map, map_element_id, on_bounds_changed_callback); + } + map.on('move', function(e) { + if (on_bounds_changed_callback !== undefined) { + call_on_bounds_chage_callback(map, map_element_id, on_bounds_changed_callback); + } + var new_map_lat = map.getCenter().lat; + var new_map_lon = map.getCenter().lng; + map.getSource('position-marker').setData({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [new_map_lon, new_map_lat] + } + }); + }); + }); + + map.on('style.load', function () { // Triggered when `setStyle` is called, add all data layers + map.loadImage('/static/bw-frontend/public/embeds/images/map_marker.png', function(error, image) { + map.addImage("custom-marker", image); + + // Add position marker + map.addLayer({ + id: "position-marker", + type: "symbol", + source: { + "type": "geojson", + "data": { + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [center_lon, center_lat] + } + } + }, + layout: { + "icon-image": "custom-marker", + "icon-ignore-placement": true, + "icon-allow-overlap": true, + } + }); + + // Update marker + var new_map_lat = map.getCenter().lat; + var new_map_lon = map.getCenter().lng; + map.getSource('position-marker').setData({ + "type": "Feature", + "geometry": { + "type": "Point", + "coordinates": [new_map_lon, new_map_lat] + } + }); + }); + }); + + return map; +} + +function toggleMapStyle(idx){ + + if (window.map !== undefined){ + var map = window.map; + } else { + // if idx is passed, it means map objects will have been saved in maps variable + var map = window.maps[idx]; + } + var map_element_id = $(map.getCanvas()).parent().parent().attr('id'); + + if (map.getStyle().sprite.indexOf(FREESOUND_STREETS_STYLE_ID) !== -1){ + // Using streets map, switch to satellite + map.setStyle('mapbox://styles/freesound/' + FREESOUND_SATELLITE_STYLE_ID); + $('#' + map_element_id).find('.map_terrain_menu')[0].innerText = 'Show streets'; + + } else { + // Using satellite map, switch to streets + map.setStyle('mapbox://styles/freesound/' + FREESOUND_STREETS_STYLE_ID); + $('#' + map_element_id).find('.map_terrain_menu')[0].innerText = 'Show terrain'; + } +} + +function latLonZoomAreValid(lat, lon, zoom){ + return !(isNaN(lat) || (isNaN(lon)) || (isNaN(zoom) || lat === '' || lon === '' || zoom === '')) +} + +function clipLatLonRanges(lat, lon){ + if ((lat < -90)){ + lat = -90; + } else if ((lat > 90)){ + lat = 90; + } + if ((lon < -180)){ + lon = -180; + } else if ((lon > 180)){ + lon = 180; + } + + return [lat, lon]; +} \ No newline at end of file diff --git a/freesound/static/bw-frontend/src/components/collapsableBlock.js b/freesound/static/bw-frontend/src/components/collapsableBlock.js index 8d8ab2a0b..f38e7f685 100644 --- a/freesound/static/bw-frontend/src/components/collapsableBlock.js +++ b/freesound/static/bw-frontend/src/components/collapsableBlock.js @@ -45,4 +45,6 @@ collapsableToggles.forEach(element => { element.addEventListener('click', handleCollapsable); }); +export {toggleCollapse}; + diff --git a/freesound/static/bw-frontend/src/pages/moderation.js b/freesound/static/bw-frontend/src/pages/moderation.js index ca10355cb..077b375be 100644 --- a/freesound/static/bw-frontend/src/pages/moderation.js +++ b/freesound/static/bw-frontend/src/pages/moderation.js @@ -1,4 +1,5 @@ import { stopAllPlayers } from '../components/player/utils' +import { toggleCollapse } from '../components/collapsableBlock' import { getCookie, setCookie } from "../utils/cookies"; const selectAllButton = document.getElementById('select-all'); @@ -20,6 +21,23 @@ const soundInfoElementsPool = document.getElementById('sound-info-elements'); const selectedSoundsInfoPanel = document.getElementById('selected-sounds-info'); +const closeCollapsableBlocks = (soundElement) => { + const collapsableToggleElement = soundElement.getElementsByClassName('collapsable-toggle')[0]; + const collapsableContainer = document.getElementById(collapsableToggleElement.dataset.target) + if (collapsableContainer.className.indexOf('-close') === -1) { + toggleCollapse(collapsableToggleElement); + } +} + +const openCollapsableBlocks = (soundElement) => { + const collapsableToggleElement = soundElement.getElementsByClassName('collapsable-toggle')[0]; + const collapsableContainer = document.getElementById(collapsableToggleElement.dataset.target) + if (collapsableContainer.className.indexOf('-close') !== -1) { + toggleCollapse(collapsableToggleElement); + } +} + + const postTicketsSelected = () => { // Do things that need to be done after some tickets have been selected such as // showing sound information, etc. @@ -58,13 +76,20 @@ const postTicketsSelected = () => { // Show information about the selected sounds // First move all selected sound info elements to the main pool + // Also "hide" the "show more info" collapsable block (if any) while (selectedSoundsInfoPanel.childNodes.length > 0) { - soundInfoElementsPool.appendChild(selectedSoundsInfoPanel.childNodes[0]); + const selectedSoundElement = selectedSoundsInfoPanel.childNodes[0]; + closeCollapsableBlocks(selectedSoundElement); + soundInfoElementsPool.appendChild(selectedSoundElement); } // Then move the selected ones to the selected panel + // If only one sound is selected, then open the "show more info" collapsable block selectedTicketsData.forEach(ticketData => { const soundInfoElement = document.querySelector(`.sound-info-element[data-sound-id="${ticketData['soundId']}"]`); + if (selectedTicketsData.length === 1) { + openCollapsableBlocks(soundInfoElement); + } selectedSoundsInfoPanel.appendChild(soundInfoElement); }); diff --git a/general/templatetags/maps_js_scripts.py b/general/templatetags/maps_js_scripts.py index c41d16c23..d35b45608 100644 --- a/general/templatetags/maps_js_scripts.py +++ b/general/templatetags/maps_js_scripts.py @@ -26,5 +26,6 @@ @register.inclusion_tag('templatetags/maps_js_scripts.html', takes_context=True) def maps_js_scripts(context): + # TODO: this will no longer be needed with BW as the equivalent is implemented in bw_templatetags return {'mapbox_access_token': settings.MAPBOX_ACCESS_TOKEN, 'media_url': settings.MEDIA_URL} diff --git a/geotags/views.py b/geotags/views.py index 711ca5b64..c241facca 100644 --- a/geotags/views.py +++ b/geotags/views.py @@ -245,7 +245,8 @@ def embed_iframe(request): 'cluster': request.GET.get('c', 'on') != 'off', 'media_url': settings.MEDIA_URL, }) - return render(request, 'geotags/geotags_box_iframe.html', tvars) + tvars.update({'mapbox_access_token': settings.MAPBOX_ACCESS_TOKEN}) + return render(request, 'embeds/geotags_box_iframe.html', tvars) def infowindow(request, sound_id): @@ -253,9 +254,11 @@ def infowindow(request, sound_id): sound = Sound.objects.select_related('user', 'geotag').get(id=sound_id) except Sound.DoesNotExist: raise Http404 - tvars = { 'sound': sound, 'minimal': request.GET.get('minimal', False) } + if request.GET.get('embed', False): + # When loading infowindow for an embed, use the template for old UI as embeds have not been updated to new UI + return render(request, 'embeds/geotags_infowindow.html', tvars) return render(request, 'geotags/infowindow.html', tvars) diff --git a/messages/tests/test_message_write.py b/messages/tests/test_message_write.py index 9d340f20d..5b02c0d81 100644 --- a/messages/tests/test_message_write.py +++ b/messages/tests/test_message_write.py @@ -119,11 +119,10 @@ def test_username_lookup_response(self): # Check username lookup view returns userames of users previously contacted by the sender or users who # previously contacted the sender self.client.force_login(self.sender) - resp = self.client.get(reverse('messages-username_lookup')) + resp = self.client.get(reverse('messages-username_lookup') + '?q=re') response_json = json.loads(resp.content) self.assertEqual(resp.status_code, 200) - self.assertCountEqual([self.receiver3.username, self.receiver2.username, self.receiver1.username, - self.sender2.username, self.sender.username], + self.assertCountEqual([self.receiver3.username, self.receiver2.username, self.receiver1.username], response_json) diff --git a/messages/views.py b/messages/views.py index 6fb0508f6..72b2f260a 100644 --- a/messages/views.py +++ b/messages/views.py @@ -178,8 +178,7 @@ def new_message(request, username=None, message_id=None): # send the user an email to notify him of the sent message! tvars = {'user_to': user_to, 'user_from': user_from} - - send_mail_template(settings.EMAIL_SUBJECT_PRIVATE_MESSAGE, 'messages/email_new_message.txt', tvars, + send_mail_template(settings.EMAIL_SUBJECT_PRIVATE_MESSAGE, 'emails/email_new_message.txt', tvars, user_to=user_to, email_type_preference_check="private_message") except: # if the email sending fails, ignore... @@ -235,8 +234,8 @@ def username_lookup(request): if not using_beastwhoosh(request): results = get_previously_contacted_usernames(request.user) else: - query = request.GET.get("q") - if query.strip(): + query = request.GET.get("q", None) + if query is not None and query.strip(): results = get_previously_contacted_usernames(request.user) results = [result for result in results if query in result] json_resp = json.dumps(results) diff --git a/ratings/tests.py b/ratings/tests.py index 342db83dd..f5aa044e4 100644 --- a/ratings/tests.py +++ b/ratings/tests.py @@ -130,19 +130,18 @@ def setUp(self): def test_rating_link_logged_in(self): """A logged in user viewing a sound should get links to rate the sound""" - self.client.force_login(self.user1) resp = self.client.get(self.sound.get_absolute_url()) - self.assertContains(resp, f'') - + self.assertContains(resp, f'') + self.assertNotContains(resp, f'') + self.assertNotContains(resp, f' - - - - \ No newline at end of file diff --git a/templates/geotags/geotags_box_iframe.html b/templates/geotags/geotags_box_iframe.html deleted file mode 100644 index cf01dc0d4..000000000 --- a/templates/geotags/geotags_box_iframe.html +++ /dev/null @@ -1,56 +0,0 @@ -{% load maps_js_scripts %} - - - - - {% maps_js_scripts %} - - - - - - - - - -
- - - - diff --git a/templates/sounds/display_sound.html b/templates/sounds/display_sound.html index cc3a2cb34..7862bd06b 100644 --- a/templates/sounds/display_sound.html +++ b/templates/sounds/display_sound.html @@ -112,7 +112,7 @@
{% endif %} {% if sound.remixgroup_id %} - + Remixes {% endif %} diff --git a/templates/sounds/sound.html b/templates/sounds/sound.html index 11e2d717c..fe6746140 100644 --- a/templates/sounds/sound.html +++ b/templates/sounds/sound.html @@ -202,7 +202,7 @@

Leave a comment:

  • Geotag
  • {% endif %} {% if sound.remix_group.all.count %} -
  • Remixes and Sources
  • +
  • Remixes and Sources
  • {% endif %} {% if sound.similarity_state == 'OK' %}
  • Similar sounds
  • diff --git a/templates_bw/accounts/login.html b/templates_bw/accounts/login.html index 25fe39359..4868c57c4 100644 --- a/templates_bw/accounts/login.html +++ b/templates_bw/accounts/login.html @@ -8,7 +8,7 @@ @@ -151,7 +151,7 @@

    {{ sound.username }} - {{ sound.original_filename|truncate_string:120 }}

    @@ -163,7 +163,7 @@

    {{ sound.username }} - {{ sound.original_filename|truncate_string:120 }}

    - avatar + avatar

    {{ sound.username }} - {{ sound.original_filename|truncate_string:120 }}

    @@ -172,13 +172,13 @@

    {{ sound.username }} - {{ sound.original_filename|truncate_string:120 }}

    - {% include "sounds/widget_license.html" %} + {% include "embeds/sound_widget_license.html" %}
    @@ -190,13 +190,12 @@

    {{ sound.username }} - {{ sound.original_filename|truncate_string:120 }}

    {% endif %} - diff --git a/templates_bw/embeds/sound_metadata.html b/templates_bw/embeds/sound_metadata.html new file mode 100644 index 000000000..55e0f9034 --- /dev/null +++ b/templates_bw/embeds/sound_metadata.html @@ -0,0 +1,7 @@ +{{sound.original_filename}} - mp3 version +{{sound.original_filename}} - ogg version +{{sound.original_filename}} - waveform +{{sound.original_filename}} - spectrogram +{{sound.duration_ms}} diff --git a/templates/sounds/sound_oembed.xml b/templates_bw/embeds/sound_oembed.xml similarity index 100% rename from templates/sounds/sound_oembed.xml rename to templates_bw/embeds/sound_oembed.xml diff --git a/templates_bw/embeds/sound_widget_license.html b/templates_bw/embeds/sound_widget_license.html new file mode 100644 index 000000000..16b0205a4 --- /dev/null +++ b/templates_bw/embeds/sound_widget_license.html @@ -0,0 +1,13 @@ +{% load static %} +{% if sound.license_name == 'Sampling+' %} + Sampling+ license +{% endif %} +{% if sound.license_name == 'Creative Commons 0' %} + Public Domain license +{% endif %} +{% if sound.license_name == 'Attribution' %} + Creative Commons Attribution license +{% endif %} +{% if sound.license_name == 'Attribution NonCommercial' %} + Creative Commons Attribution Non-Commercial license +{% endif %} diff --git a/templates_bw/molecules/navbar_common_actions.html b/templates_bw/molecules/navbar_common_actions.html index 3276f571e..04e6dbebc 100644 --- a/templates_bw/molecules/navbar_common_actions.html +++ b/templates_bw/molecules/navbar_common_actions.html @@ -44,6 +44,9 @@ + {% comment %} - diff --git a/templates/oauth2_provider/authorize.html b/templates_bw/oauth2_provider/authorize.html similarity index 96% rename from templates/oauth2_provider/authorize.html rename to templates_bw/oauth2_provider/authorize.html index 223074ee9..ff6714b1c 100644 --- a/templates/oauth2_provider/authorize.html +++ b/templates_bw/oauth2_provider/authorize.html @@ -1,3 +1,4 @@ +{% load static %} {% load apiv2_templatetags %} @@ -40,7 +41,7 @@
    - + {% if not error %}

    The application {{ application.name }} is requesting
    permission to access your data.

    diff --git a/templates_bw/oauth2_provider/oauth_login.html b/templates_bw/oauth2_provider/oauth_login.html new file mode 100644 index 000000000..c8978a77b --- /dev/null +++ b/templates_bw/oauth2_provider/oauth_login.html @@ -0,0 +1,69 @@ +{% load static %} + + + + + + Freesound - login + + + + +
    +
    + + {% csrf_token %} + {{form.as_p}} + + + +

    Do not have a Freesound account? Register here

    +
    +
    + + + diff --git a/templates/rest_framework/api.html b/templates_bw/rest_framework/api.html similarity index 100% rename from templates/rest_framework/api.html rename to templates_bw/rest_framework/api.html diff --git a/templates_bw/sounds/display_pack.html b/templates_bw/sounds/display_pack.html index ee8a81df1..80ba001be 100644 --- a/templates_bw/sounds/display_pack.html +++ b/templates_bw/sounds/display_pack.html @@ -47,9 +47,9 @@
    {% if pack.show_unpublished_sounds_warning %} {% with pack.num_sounds_unpublished as num_sounds_unpublished %} - {% if num_sounds_unpublished %} -
    - {% bw_icon "notification" %}Has {{ num_sounds_unpublished}} unpublished sounds + {% if num_sounds_unpublished > 0 %} +
    + {% bw_icon "notification" %}Has {{ num_sounds_unpublished}} unpublished sound{{ num_sounds_unpublished|pluralize }}
    {% endif %} {% endwith %} diff --git a/templates_bw/sounds/display_sound.html b/templates_bw/sounds/display_sound.html index ffbb0f08a..feda1f670 100644 --- a/templates_bw/sounds/display_sound.html +++ b/templates_bw/sounds/display_sound.html @@ -119,7 +119,7 @@
    - + {{ sound.num_downloads|formatnumber }}
    @@ -165,9 +165,9 @@
    - +
    - {% if not is_explicit %} + {% if is_explicit %}
    {% bw_icon "notification" %} Marked as explicit diff --git a/templates/sounds/pack_attribution.txt b/templates_bw/sounds/pack_attribution.txt similarity index 100% rename from templates/sounds/pack_attribution.txt rename to templates_bw/sounds/pack_attribution.txt diff --git a/templates_bw/sounds/player.html b/templates_bw/sounds/player.html index e91ffe77b..45ce2f0a9 100644 --- a/templates_bw/sounds/player.html +++ b/templates_bw/sounds/player.html @@ -8,7 +8,7 @@ data-similar-sounds="{% if show_similar_sounds_button and sound.similarity_state == 'OK' %}true{% else %}false{% endif %}" data-similar-sounds-modal-url="{% if show_similar_sounds_button %}{% url 'sound-similar' sound.username sound.id %}?ajax=1{% endif %}" data-remix-group="{% if show_remix_group_button and sound.remixgroup_id %}true{% else %}false{% endif %}" - data-remix-group-modal-url="{% if show_remix_group_button %}{% url 'sound-remixes' sound.username sound.id %}{% endif %}" + data-remix-group-modal-url="{% if show_remix_group_button %}{% url 'sound-remixes' sound.username sound.id %}?ajax=1{% endif %}" data-mp3="{{ sound.locations.preview.LQ.mp3.url }}" data-ogg="{{ sound.locations.preview.LQ.ogg.url }}" data-waveform="{% if player_size != 'big_no_info' %}{{ sound.locations.display.wave_bw.M.url }}{% else %}{{ sound.locations.display.wave_bw.L.url }}{% endif %}" @@ -18,5 +18,7 @@ data-samplerate="{{ sound.samplerate }}" data-show-milliseconds="{{ show_milliseconds }}" data-favorite="false" + data-num-comments="{{ sound.num_comments }}" {% comment %}currently unused{% endcomment %} + data-num-downloads="{{ sound.num_downloads }}" {% comment %}currently unused{% endcomment %} tabindex="0"> {% comment %}using tabindex so screen readers cycle sound lists with tab key{% endcomment %}
    \ No newline at end of file diff --git a/templates_bw/sounds/sound.html b/templates_bw/sounds/sound.html index df8ea6566..2ec85169e 100644 --- a/templates_bw/sounds/sound.html +++ b/templates_bw/sounds/sound.html @@ -62,7 +62,7 @@

    {% if sound.remix_group.all.count %} - {% endif %} @@ -203,8 +203,8 @@

    + {% if request.user.is_authenticated and sound.user != request.user %} +

    Your rating:

    {% bw_sound_stars sound True True True %} @@ -212,7 +212,7 @@

    +
    {% if sound.moderation_state == 'OK' and sound.processing_state == 'OK' %} {% if request.user.is_authenticated %} Download sound diff --git a/tickets/models.py b/tickets/models.py index 705c968f2..1014af113 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -56,12 +56,12 @@ class Ticket(models.Model): queue = models.ForeignKey(Queue, related_name='tickets', on_delete=models.CASCADE) sound = models.OneToOneField('sounds.Sound', null=True, on_delete=models.SET_NULL) - NOTIFICATION_QUESTION = 'tickets/email_notification_question.txt' - NOTIFICATION_APPROVED = 'tickets/email_notification_approved.txt' - NOTIFICATION_APPROVED_BUT = 'tickets/email_notification_approved_but.txt' - NOTIFICATION_DELETED = 'tickets/email_notification_deleted.txt' - NOTIFICATION_UPDATED = 'tickets/email_notification_updated.txt' - NOTIFICATION_WHITELISTED = 'tickets/email_notification_whitelisted.txt' + NOTIFICATION_QUESTION = 'emails/email_notification_question.txt' + NOTIFICATION_APPROVED = 'emails/email_notification_approved.txt' + NOTIFICATION_APPROVED_BUT = 'emails/email_notification_approved_but.txt' + NOTIFICATION_DELETED = 'emails/email_notification_deleted.txt' + NOTIFICATION_UPDATED = 'emails/email_notification_updated.txt' + NOTIFICATION_WHITELISTED = 'emails/email_notification_whitelisted.txt' MODERATOR_ONLY = 1 USER_ONLY = 2 diff --git a/utils/frontend_handling.py b/utils/frontend_handling.py index abff74df8..a88a782df 100644 --- a/utils/frontend_handling.py +++ b/utils/frontend_handling.py @@ -53,22 +53,15 @@ def render(request, template_name, context=None, content_type=None, status=None, This wrapper is intented to be use while we implement the new frontend. Once the whole implementation process is finished and we don't need the two frontends to coexist anymore, then we can get rid of this wrapper. - """ + NOTE: as BW implementation is now complete and we're in the process of releasing it, this method has been + simplified to not fallback to any default frontend and simply choose the appropriate frontend folder + based on the session variable. + """ # Get the name of the template engine/frontend name = selected_frontend(request) - - try: - return django_render(request, template_name, context, content_type, status, using=name) - except TemplateDoesNotExist: - - if name != settings.FRONTEND_DEFAULT: - # If the required template does can't be found using the selected engine, try with the default engine - return django_render(request, template_name, context, content_type, status, using=settings.FRONTEND_DEFAULT) - else: - # If the default engine was being used, then raise the exception normally - raise - + return django_render(request, template_name, context, content_type, status, using=name) + def using_frontend(request, frontend_name): """ diff --git a/utils/test_helpers.py b/utils/test_helpers.py index 76956cbf9..08770113a 100644 --- a/utils/test_helpers.py +++ b/utils/test_helpers.py @@ -19,6 +19,7 @@ # import os +import pickle from functools import partial, wraps from itertools import count @@ -63,7 +64,8 @@ def create_test_files(filenames=None, directory=None, paths=None, n_bytes=1024, def create_user_and_sounds(num_sounds=1, num_packs=0, user=None, count_offset=0, tags=None, - processing_state='PE', moderation_state='PE', type='wav'): + description=None, processing_state='PE', moderation_state='PE', type='wav', + username="testuser"): """Creates User, Sound and Pack objects useful for testing. A counter is used to make sound names unique as well as other fields like md5 (see `sound_counter` variable). @@ -76,6 +78,7 @@ def create_user_and_sounds(num_sounds=1, num_packs=0, user=None, count_offset=0, user (User): user owner of the created sounds (if not provided, a new user will be created). count_offset (int): start counting sounds at X. tags (str or None): string of tags to be added to the sounds (all sounds will have the same tags). + description (str or None): description to be added to the sounds (all sounds will have the same description). processing_state (str): processing state of the created sounds. moderation_state (str): moderation state of the created sounds. type (str): type of the sounds to be created (e.g. 'wav'). @@ -86,7 +89,7 @@ def create_user_and_sounds(num_sounds=1, num_packs=0, user=None, count_offset=0, """ count_offset = count_offset + next(sound_counter) if user is None: - user = User.objects.create_user("testuser", password="testpass", email='email@freesound.org') + user = User.objects.create_user(username, password="testpass", email=f'{username}@freesound.org') packs = list() for i in range(0, num_packs): pack = Pack.objects.create(user=user, name="Test pack %i" % (i + count_offset)) @@ -99,7 +102,8 @@ def create_user_and_sounds(num_sounds=1, num_packs=0, user=None, count_offset=0, sound = Sound.objects.create(user=user, original_filename="Test sound %i" % (i + count_offset), base_filename_slug="test_sound_%i" % (i + count_offset), - license=License.objects.all()[0], + license=License.objects.last(), + description=description if description is not None else '', pack=pack, md5="fakemd5_%i" % (i + count_offset), type=type, @@ -112,12 +116,6 @@ def create_user_and_sounds(num_sounds=1, num_packs=0, user=None, count_offset=0, return user, packs, sounds -def test_using_bw_ui(test_case_object): - session = test_case_object.client.session - session[settings.FRONTEND_SESSION_PARAM_NAME] = settings.FRONTEND_BEASTWHOOSH - session.save() - - def override_path_with_temp_directory(fun, settings_path_name): """ Decorator that wraps a function inside two context managers which i) create a temporary directory; and ii) override @@ -163,3 +161,8 @@ def ret_fun(*args, **kwargs): override_processing_before_description_path_with_temp_directory = \ partial(override_path_with_temp_directory, settings_path_name='PROCESSING_BEFORE_DESCRIPTION_DIR') + + +def create_fake_perform_search_engine_query_results_tags_mode(): + # This returns utils.search.SearchResults which was pickled from a real query in a local freesound instance + return pickle.loads(b'\x80\x04\x95\x1c\x07\x00\x00\x00\x00\x00\x00\x8c\x0cutils.search\x94\x8c\rSearchResults\x94\x93\x94)\x81\x94}\x94(\x8c\x04docs\x94]\x94}\x94(\x8c\x02id\x94J\xef\x8b\x05\x00\x8c\x05score\x94G?\xf0\x00\x00\x00\x00\x00\x00\x8c\x0fn_more_in_group\x94K\x00\x8c\ngroup_docs\x94]\x94}\x94(\x8c\x02id\x94J\xef\x8b\x05\x00\x8c\x05score\x94G?\xf0\x00\x00\x00\x00\x00\x00ua\x8c\ngroup_name\x94\x8c\x06363503\x94ua\x8c\x06facets\x94}\x94\x8c\x03tag\x94]\x94(\x8c\x0ffield-recording\x94M\xa3\x07\x86\x94\x8c\x05noise\x94M\x86\x07\x86\x94\x8c\nelectronic\x94M\x0c\x07\x86\x94\x8c\x05voice\x94M\xc8\x05\x86\x94\x8c\x05metal\x94M8\x05\x86\x94\x8c\x04loop\x94M\x08\x05\x86\x94\x8c\x06effect\x94M\xed\x04\x86\x94\x8c\x05sound\x94M\xe4\x04\x86\x94\x8c\x04bass\x94M\xd7\x03\x86\x94\x8c\x05water\x94M\xd3\x03\x86\x94\x8c\x04male\x94M\xd0\x03\x86\x94\x8c\x02fx\x94M\xc4\x03\x86\x94\x8c\x04drum\x94Mf\x03\x86\x94\x8c\x07ambient\x94MS\x03\x86\x94\x8c\x05synth\x94M\x0b\x03\x86\x94\x8c\x03sfx\x94M\xf5\x02\x86\x94\x8c\npercussion\x94M\xe6\x02\x86\x94\x8c\x08ambience\x94M\xc3\x02\x86\x94\x8c\x0bmultisample\x94M\xb5\x02\x86\x94\x8c\nmezzoforte\x94M\xb2\x02\x86\x94\x8c\x04beat\x94M\xac\x02\x86\x94\x8c\x05words\x94M\xa6\x02\x86\x94\x8c\x06female\x94M\x80\x02\x86\x94\x8c\x06glitch\x94Mw\x02\x86\x94\x8c\x08zoom-h2n\x94Md\x02\x86\x94\x8c\x06nature\x94MV\x02\x86\x94\x8c\x07machine\x94MA\x02\x86\x94\x8c\x06speech\x94M=\x02\x86\x94\x8c\x04kick\x94M9\x02\x86\x94\x8c\x06sci-fi\x94M.\x02\x86\x94\x8c\x05remix\x94M"\x02\x86\x94\x8c\x03hit\x94M\x1a\x02\x86\x94\x8c\x04door\x94M\xfe\x01\x86\x94\x8c\x03mix\x94M\xfd\x01\x86\x94\x8c\x0cexperimental\x94M\xf5\x01\x86\x94\x8c\x05close\x94M\xe4\x01\x86\x94\x8c\x05birds\x94M\xe3\x01\x86\x94\x8c\x10flexible-grooves\x94M\xdf\x01\x86\x94\x8c\nsoundscape\x94M\xd7\x01\x86\x94\x8c\x0bnon-vibrato\x94M\xd5\x01\x86\x94\x8c\x0bchordophone\x94M\xd2\x01\x86\x94\x8c\nindustrial\x94M\xd1\x01\x86\x94\x8c\x0bsynthesizer\x94M\xd1\x01\x86\x94\x8c\x0fmtc500-m002-s14\x94M\xc9\x01\x86\x94\x8c\x04wood\x94M\xc2\x01\x86\x94\x8c\x04game\x94M\xa8\x01\x86\x94\x8c\x06engine\x94M\xa5\x01\x86\x94\x8c\x07english\x94M\xa4\x01\x86\x94\x8c\x04dark\x94M\x99\x01\x86\x94\x8c\nsound-trip\x94M\x95\x01\x86\x94\x8c\x06horror\x94M\x91\x01\x86\x94\x8c\x06guitar\x94M\x86\x01\x86\x94\x8c\x05drone\x94M\x84\x01\x86\x94\x8c\x06impact\x94M\x82\x01\x86\x94\x8c\trecording\x94M\x80\x01\x86\x94\x8c\x08electric\x94M}\x01\x86\x94\x8c\x05click\x94M{\x01\x86\x94\x8c\x05space\x94Mz\x01\x86\x94\x8c\x06sample\x94Mv\x01\x86\x94\x8c\natmosphere\x94Mu\x01\x86\x94\x8c\x0belectronica\x94Mu\x01\x86\x94\x8c\x05short\x94Mn\x01\x86\x94\x8c\x04wind\x94Me\x01\x86\x94\x8c\x06puzzle\x94Mc\x01\x86\x94\x8c\x04bell\x94Mb\x01\x86\x94\x8c\x07whisper\x94Mb\x01\x86\x94\x8c\x11string-instrument\x94M_\x01\x86\x94\x8c\x06mumble\x94M]\x01\x86\x94\x8c\x08alphabet\x94M[\x01\x86\x94\x8c\x0cbrian-cimmet\x94M[\x01\x86\x94\x8c\x0ccelia-madeoy\x94M[\x01\x86\x94\x8c\tcrossword\x94M[\x01\x86\x94\x8c\x0cdaniel-feyer\x94M[\x01\x86\x94\x8c\rdoug-peterson\x94M[\x01\x86\x94\x8c\x08gridplay\x94M[\x01\x86\x94\x8c\x07letters\x94M[\x01\x86\x94\x8c\x0emalcolm-ingram\x94M[\x01\x86\x94\x8c\x07solving\x94M[\x01\x86\x94\x8c\x0estanley-newman\x94M[\x01\x86\x94\x8c\x05vocal\x94M[\x01\x86\x94\x8c\x07digital\x94MR\x01\x86\x94\x8c\x03owi\x94ML\x01\x86\x94\x8c\x05scary\x94ML\x01\x86\x94\x8c\tdistorted\x94MJ\x01\x86\x94\x8c\x05motor\x94MI\x01\x86\x94\x8c\x04flex\x94ME\x01\x86\x94\x8c\x08computer\x94MD\x01\x86\x94\x8c\x04wave\x94MD\x01\x86\x94\x8c\x06reverb\x94MC\x01\x86\x94\x8c\x03car\x94MA\x01\x86\x94\x8c\x03low\x94M@\x01\x86\x94\x8c\ndistortion\x94M=\x01\x86\x94\x8c\nmechanical\x94M=\x01\x86\x94\x8c\x0csound-design\x94M9\x01\x86\x94\x8c\x06tenuto\x94M9\x01\x86\x94\x8c\x05drums\x94M8\x01\x86\x94\x8c\x04name\x94M4\x01\x86\x94\x8c\nportuguese\x94M4\x01\x86\x94\x8c\x12iberian-portuguese\x94M2\x01\x86\x94\x8c\x04hard\x94M0\x01\x86\x94es\x8c\x0chighlighting\x94}\x94\x8c\x1dnon_grouped_number_of_results\x94M\x92c\x8c\tnum_found\x94M\xb6$\x8c\x05start\x94K\x00\x8c\x08num_rows\x94K\x01\x8c\x06q_time\x94K\x10ub.') \ No newline at end of file diff --git a/wiki/tests.py b/wiki/tests.py index 2503d55db..5802c4472 100644 --- a/wiki/tests.py +++ b/wiki/tests.py @@ -50,7 +50,7 @@ def test_admin_page(self): # An admin user has a link to edit the page self.client.force_login(self.user) resp = self.client.get(reverse('wiki-page', kwargs={'name': 'help'})) - self.assertContains(resp, 'edit this page') + self.assertContains(resp, 'Edit this page') def test_page_version(self): helpurl = reverse('wiki-page', kwargs={'name': 'help'}) @@ -126,8 +126,8 @@ def test_edit_page_no_page(self): self.client.force_login(self.user1) resp = self.client.get(reverse('wiki-page-edit', kwargs={'name': 'notapage'})) - self.assertContains(resp, '', html=True) - self.assertContains(resp, '', html=True) + self.assertContains(resp, 'placeholder="Contents of the page. You can use Markdown formatting and HTML."') + self.assertContains(resp, 'placeholder="Title of the page"') def test_edit_page_save(self): # POST to the form and a new Content for this page is created