diff --git a/django_ace/widgets.py b/django_ace/widgets.py index ec4df40..659ec75 100644 --- a/django_ace/widgets.py +++ b/django_ace/widgets.py @@ -66,7 +66,7 @@ class AceWidget(forms.Textarea): if self.toolbar: toolbar = ( '
' - '' + '' "
" ).format(self.width) html = toolbar + html diff --git a/judge/views/organization.py b/judge/views/organization.py index 54d6adb..0a3fc31 100644 --- a/judge/views/organization.py +++ b/judge/views/organization.py @@ -671,21 +671,13 @@ class OrganizationRequestBaseView( LoginRequiredMixin, SingleObjectTemplateResponseMixin, SingleObjectMixin, + AdminOrganizationMixin, ): model = Organization slug_field = "key" slug_url_kwarg = "key" tab = None - def get_object(self, queryset=None): - organization = super(OrganizationRequestBaseView, self).get_object(queryset) - if not ( - organization.admins.filter(id=self.request.profile.id).exists() - or organization.registrant_id == self.request.profile.id - ): - raise PermissionDenied() - return organization - def get_content_title(self): return _("Manage join requests") diff --git a/resources/common.js b/resources/common.js index e89061f..3c4e138 100644 --- a/resources/common.js +++ b/resources/common.js @@ -386,44 +386,230 @@ function activateBlogBoxOnClick() { } function changeTabParameter(newTab) { - const url = new URL(window.location); - const searchParams = new URLSearchParams(url.search); - searchParams.set('tab', newTab); - searchParams.delete('page'); - url.search = searchParams.toString(); - return url.href; + const url = new URL(window.location); + const searchParams = new URLSearchParams(url.search); + searchParams.set('tab', newTab); + searchParams.delete('page'); + url.search = searchParams.toString(); + return url.href; } function submitFormWithParams($form, method) { - const currentUrl = new URL(window.location.href); - const searchParams = new URLSearchParams(currentUrl.search); - const formData = $form.serialize(); + const currentUrl = new URL(window.location.href); + const searchParams = new URLSearchParams(currentUrl.search); + const formData = $form.serialize(); - const params = new URLSearchParams(formData); + const params = new URLSearchParams(formData); - if (searchParams.has('tab')) { - params.set('tab', searchParams.get('tab')); - } + if (searchParams.has('tab')) { + params.set('tab', searchParams.get('tab')); + } - const fullUrl = currentUrl.pathname + '?' + params.toString(); + const fullUrl = currentUrl.pathname + '?' + params.toString(); - if (method === "GET") { - window.location.href = fullUrl; - } - else { - var $formToSubmit = $('
') - .attr('action', fullUrl) - .attr('method', 'POST') - .appendTo('body'); + if (method === "GET") { + window.location.href = fullUrl; + } + else { + var $formToSubmit = $('') + .attr('action', fullUrl) + .attr('method', 'POST') + .appendTo('body'); - $formToSubmit.append($('').attr({ - type: 'hidden', - name: 'csrfmiddlewaretoken', - value: $.cookie('csrftoken') - })); + $formToSubmit.append($('').attr({ + type: 'hidden', + name: 'csrfmiddlewaretoken', + value: $.cookie('csrftoken') + })); - $formToSubmit.submit(); - } + $formToSubmit.submit(); + } +} + +function saveCurrentPageToSessionStorage() { + const storageLimit = 5; + const sessionStorageKey = 'oj-content-keys'; + + let key = `oj-content-${window.location.href}`; + let $contentClone = $('body').clone(); + $contentClone.find('.select2').remove(); + $contentClone.find('.select2-hidden-accessible').removeClass('select2-hidden-accessible'); + $contentClone.find('.noUi-base').remove(); + $contentClone.find('.wmd-button-row').remove(); + + let contentData = JSON.stringify({ + "html": $contentClone.html(), + "page": window.page, + "has_next_page": window.has_next_page, + "scrollOffset": $(window).scrollTop(), + }); + + let keys = JSON.parse(sessionStorage.getItem(sessionStorageKey)) || []; + + // Remove the existing key if it exists + if (keys.includes(key)) { + keys = keys.filter(k => k !== key); + } + + keys.push(key); + + if (keys.length > storageLimit) { + let oldestKey = keys.shift(); + sessionStorage.removeItem(oldestKey); + } + + sessionStorage.setItem(sessionStorageKey, JSON.stringify(keys)); + sessionStorage.setItem(key, contentData); +} + +function loadPageFromSessionStorage() { + let key = `oj-content-${window.location.href}`; + let content = sessionStorage.getItem(key); + if (content) { + content = JSON.parse(content); + $('body').html(content.html); + onWindowReady(); + window.PAGE_FROM_BACK_BUTTON_CACHE = true; + setTimeout(() => { + $(window).scrollTop(content.scrollOffset - 50); + }, 1); + window.page = content.page; + window.has_next_page = content.has_next_page; + } +} + +let currentRequest = null; // Variable to keep track of the current request + +function navigateTo(url, reload_container, force_new_page=false) { + if (url === '#') return; + if (force_new_page) { + window.location.href = url; + return; + } + + if (!reload_container || !$(reload_container).length) { + reload_container = "#content"; + } + + if (currentRequest) { + currentRequest.abort(); + } + + const controller = new AbortController(); + const signal = controller.signal; + currentRequest = controller; + + $(window).off("scroll"); + + saveCurrentPageToSessionStorage(); + replaceLoadingPage(reload_container); + + const toUpdateElements = [ + "#nav-container", + "#js_media", + "#media", + ".left-sidebar", + "#bodyend", + ]; + + $.ajax({ + url: url, + method: 'GET', + dataType: 'html', + signal: signal, + success: function (data) { + let reload_content = $(data).find(reload_container).first(); + if (!reload_content.length) { + reload_container = "#content"; + reload_content = $(data).find(reload_container).first(); + } + + if (reload_content.length) { + window.history.pushState("", "", url); + $(window).scrollTop(0); + $("#loading-bar").stop(true, true); + $("#loading-bar").hide().css({ width: 0}); + + for (let elem of toUpdateElements) { + $(elem).replaceWith($(data).find(elem).first()); + } + + $(reload_container).replaceWith(reload_content); + + $(document).prop('title', $(data).filter('title').text()); + renderKatex($(reload_container)[0]); + onWindowReady(); + $('.xdsoft_datetimepicker').hide(); + } else { + window.location.href = url; + } + }, + error: function (xhr, status, error) { + if (status !== 'abort') { // Ignore aborted requests + window.location.href = url; + } + } + }); +} + +function isEligibleLinkToRegister($e) { + if ($e.attr('target') === '_blank') { + return false; + } + if ($e.data('initialized_navigation')) { + return false; + } + const href = $e.attr('href'); + if (!href) return false; + return ( + href.startsWith('http') + || href.startsWith('/') + || href.startsWith('#') + || href.startsWith('?') + ); +}; + +function replaceLoadingPage(reload_container) { + const loadingPage = ` +
+ +
+ `; + $(reload_container).fadeOut(100, function() { + $(this).html(loadingPage).fadeIn(100); + }); +} + +function registerNavigation() { + const containerMap = { + '.left-sidebar-item': '.middle-right-content', + '.pagination li a': '.middle-content', + '.tabs li a': '.middle-content', + '#control-panel a': '.middle-content', + }; + + $.each(containerMap, function(selector, target) { + $(selector).each(function() { + const href = $(this).attr('href'); + const force_new_page = $(this).data('force_new_page'); + + if (isEligibleLinkToRegister($(this))) { + $(this).data('initialized_navigation', true); + + $(this).on('click', function(e) { + e.preventDefault(); + let containerSelector = null; + for (let key in containerMap) { + if ($(this).is(key)) { + containerSelector = containerMap[key]; + break; + } + } + navigateTo(href, containerSelector, force_new_page); + }); + } + }); + }); } function onWindowReady() { @@ -528,7 +714,7 @@ function onWindowReady() { $("#loading-bar").animate({ width: "100%" }, 2000, function() { $(this).stop(true, true); $(this).hide().css({ width: 0}); - }); + }); }); $('.errorlist').each(function() { @@ -537,12 +723,13 @@ function onWindowReady() { }); register_all_toggles(); activateBlogBoxOnClick(); + registerNavigation(); + registerPopper($('#nav-lang-icon'), $('#lang-dropdown')); + registerPopper($('#user-links'), $('#userlink_dropdown')); } $(function() { onWindowReady(); - registerPopper($('#nav-lang-icon'), $('#lang-dropdown')); - registerPopper($('#user-links'), $('#userlink_dropdown')); var $nav_list = $('#nav-list'); $('#navicon').click(function (event) { event.stopPropagation(); @@ -582,33 +769,15 @@ $(function() { $nav_list.hide(); }); - $(window).on('beforeunload', function() { - let key = `oj-content-${window.location.href}`; - let $contentClone = $('#content').clone(); - $contentClone.find('.select2').remove(); - $contentClone.find('.select2-hidden-accessible').removeClass('select2-hidden-accessible'); - $contentClone.find('.noUi-base').remove(); - $contentClone.find('.wmd-button-row').remove(); - sessionStorage.setItem(key, JSON.stringify({ - "html": $contentClone.html(), - "page": window.page, - "has_next_page": window.has_next_page, - "scrollOffset": $(window).scrollTop(), - })); - }); + $(window).on('beforeunload', saveCurrentPageToSessionStorage); + if (window.performance && window.performance.navigation.type === window.performance.navigation.TYPE_BACK_FORWARD) { - let key = `oj-content-${window.location.href}`; - let content = sessionStorage.getItem(key); - if (content) { - content = JSON.parse(content); - $('#content').html(content.html); - onWindowReady(); - window.PAGE_FROM_BACK_BUTTON_CACHE = true; - $(window).scrollTop(content.scrollOffset - 100); - window.page = content.page; - window.has_next_page = content.has_next_page; - } + loadPageFromSessionStorage(); } + + window.addEventListener('popstate', (e) => { + window.location.href = e.currentTarget.location.href; + }); }); \ No newline at end of file diff --git a/resources/html/loading-page.html b/resources/html/loading-page.html index bf1813e..8f9ec8a 100644 --- a/resources/html/loading-page.html +++ b/resources/html/loading-page.html @@ -1 +1,22 @@ -
L
Q
D
O
J
.
.
.
L
Q
D
O
J
\ No newline at end of file + +
+
+ L + Q + D + O + J +
+
\ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 969558c..ec766e1 100644 --- a/templates/base.html +++ b/templates/base.html @@ -67,7 +67,7 @@ } {% endif %} - {% block media %}{% endblock %} + {% if use_darkmode %} @@ -210,7 +210,7 @@ {% if request.user.is_impersonate %} - + {% else %} @@ -261,6 +261,9 @@ {% endif %}
+
+ {% block media %}{% endblock %} +
@@ -303,8 +306,9 @@ }); {% endcompress %} - - {% block js_media %}{% endblock %} +
+ {% block js_media %}{% endblock %} +
{% if request.in_contest %} {% include "katex-load.html" %} {% block footer %} diff --git a/templates/blog/blog.html b/templates/blog/blog.html index de3f759..66b99fb 100644 --- a/templates/blog/blog.html +++ b/templates/blog/blog.html @@ -46,8 +46,3 @@
{% include "comments/list.html" %} {% endblock %} - -{% block bodyend %} - {{ super() }} - {% include "comments/math.html" %} -{% endblock %} \ No newline at end of file diff --git a/templates/blog/list.html b/templates/blog/list.html index cb54692..4ac69b1 100644 --- a/templates/blog/list.html +++ b/templates/blog/list.html @@ -19,7 +19,7 @@ {% endblock %} -{% block three_col_js %} +{% block js_media %} {% include "actionbar/media-js.html" %} {% include "feed/feed_js.html" %} {% compress js %} @@ -41,11 +39,12 @@ return receiver; } - let message_template = ` + function message_template() { + return` {% with message=message_template %} {% include "chat/message.html" %} {% endwith %} - `; + `}; $(function() { load_dynamic_update({{last_msg}}); }); diff --git a/templates/chat/chat_js.html b/templates/chat/chat_js.html index 70c52a0..e98517f 100644 --- a/templates/chat/chat_js.html +++ b/templates/chat/chat_js.html @@ -1,5 +1,7 @@ -{% endcompress %} diff --git a/templates/contest/contest.html b/templates/contest/contest.html index ceac37f..19e23a3 100644 --- a/templates/contest/contest.html +++ b/templates/contest/contest.html @@ -10,7 +10,7 @@ {% include "contest/contest-tabs.html" %} {% endblock %} -{% block two_col_js %} +{% block js_media %} {% include "contest/media-js.html" %} {% include "comments/media-js.html" %} {% include "actionbar/media-js.html" %} diff --git a/templates/contest/list.html b/templates/contest/list.html index c7a0995..78a2de5 100644 --- a/templates/contest/list.html +++ b/templates/contest/list.html @@ -31,13 +31,14 @@ {% block contest_list_media %}{% endblock %} {% endblock %} -{% block three_col_js %} +{% block js_media %} diff --git a/templates/course/edit_lesson.html b/templates/course/edit_lesson.html index 257fbe1..2cfe84b 100644 --- a/templates/course/edit_lesson.html +++ b/templates/course/edit_lesson.html @@ -12,7 +12,7 @@ {% endblock %} -{% block two_col_js %} +{% block js_media %} {{ form.media.js }} {% endblock %} diff --git a/templates/course/grades.html b/templates/course/grades.html index 0962785..a560cc0 100644 --- a/templates/course/grades.html +++ b/templates/course/grades.html @@ -18,7 +18,7 @@ {% endblock %} -{% block two_col_js %} +{% block js_media %} \ No newline at end of file diff --git a/templates/organization/list.html b/templates/organization/list.html index 8d6108e..2efd915 100644 --- a/templates/organization/list.html +++ b/templates/organization/list.html @@ -1,11 +1,12 @@ {% extends "three-column-content.html" %} -{% block three_col_js %} +{% block js_media %} {% endcompress %} {% if dynamic_update and last_msg and not has_hidden_subtasks %} diff --git a/templates/tabs-base.html b/templates/tabs-base.html index 1e6370a..7b8dd18 100644 --- a/templates/tabs-base.html +++ b/templates/tabs-base.html @@ -1,6 +1,6 @@ -{% macro make_tab(name, fa, url, text) %} +{% macro make_tab(name, fa, url, text, force_new_page=False) %}
  • - {%- if url %}{% else %}{% endif -%} + {%- if url %}{% else %}{% endif -%} {{ text }} {%- if url %}{% else %}{% endif -%}
  • diff --git a/templates/three-column-content.html b/templates/three-column-content.html index cceaaf7..9b90319 100644 --- a/templates/three-column-content.html +++ b/templates/three-column-content.html @@ -26,86 +26,6 @@ {% endblock %} -{% block js_media %} - -{% endblock %} - {% macro make_tab_item(name, fa, url, text, force_new_page=False) %} @@ -128,12 +48,3 @@
    {% block after_posts %}{% endblock %} {% endblock %} - -{% block extra_js %} - {% block three_col_js %}{% endblock %} -{% endblock %} - -{% block bodyend %} - {{ super() }} - {% include "comments/math.html" %} -{% endblock %} \ No newline at end of file diff --git a/templates/ticket/ticket.html b/templates/ticket/ticket.html index f5a2009..52ce655 100644 --- a/templates/ticket/ticket.html +++ b/templates/ticket/ticket.html @@ -223,8 +223,3 @@ {% endblock %} - -{% block bodyend %} - {{ super() }} - {% include "comments/math.html" %} -{% endblock %} diff --git a/templates/two-column-content.html b/templates/two-column-content.html index 3c86d27..2b1ed79 100644 --- a/templates/two-column-content.html +++ b/templates/two-column-content.html @@ -1,10 +1,6 @@ {% set is_two_column = true %} {% extends "three-column-content.html" %} -{% block three_col_js %} - {% block two_col_js %}{% endblock %} -{% endblock %} - {% block three_col_media %}