Add CSS animations and minimal auto-focus JS with graceful fallback #33
8 changed files with 422 additions and 97 deletions
205
docs/JAVASCRIPT.rst
Normal file
205
docs/JAVASCRIPT.rst
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
JavaScript Usage in Remarkbox
|
||||
=============================
|
||||
|
||||
This document catalogs all JavaScript usage in the Remarkbox codebase.
|
||||
|
||||
Standalone JavaScript Files
|
||||
---------------------------
|
||||
|
||||
remarkbox/static/js/custom.js
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Main application JavaScript containing core functionality. No external
|
||||
dependencies (jQuery was removed).
|
||||
|
||||
**previewAjax()** (Lines 5-20)
|
||||
Debounced preview function with 800ms timer. Escapes HTML in raw mode
|
||||
to prevent XSS, then calls sendPreview().
|
||||
|
||||
**sendPreview()** (Lines 22-41)
|
||||
Fetch request to ``/preview-post`` endpoint for Markdown rendering.
|
||||
Includes ``X-Requested-With: XMLHttpRequest`` header required by server.
|
||||
Optionally triggers MathJax re-rendering.
|
||||
|
||||
**toggle()** (Lines 43-66)
|
||||
CSS-based toggle animation. Adds/removes ``toggle-open`` and
|
||||
``toggle-closing`` classes. Updates button text after 800ms animation.
|
||||
Triggers textarea auto-grow on open if content exists.
|
||||
|
||||
**Details close animation** (Lines 68-83)
|
||||
Event listener for ``.preview-toggle`` clicks. Animates ``<details>``
|
||||
element closure over 800ms using ``closing`` class.
|
||||
|
||||
**autoGrow()** (Lines 85-89)
|
||||
Auto-grows textarea height based on content, capped at 400px.
|
||||
|
||||
**Document ready handler** (Lines 91-130)
|
||||
- Binds input handlers for textarea auto-grow
|
||||
- Binds vote-up/vote-down button click handlers
|
||||
- Fades in alert elements over 2 seconds
|
||||
- Highlights URL fragment targets with ``focused`` class
|
||||
|
||||
**sendVote()** (Lines 132-151)
|
||||
Fetch request to ``/vote-post`` endpoint. Updates vote count on success.
|
||||
|
||||
remarkbox/static/js/iframe-resizer/
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
External library for responsive iframe sizing in embed mode.
|
||||
|
||||
- ``iframeResizer.min.js`` - Main resizer script
|
||||
- ``iframeResizer.contentWindow.min.js`` - Content window script
|
||||
|
||||
|
||||
Inline JavaScript in Templates
|
||||
------------------------------
|
||||
|
||||
Form Submission Protection
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pattern: ``onsubmit="submit.disabled = true; return true;"``
|
||||
|
||||
Disables submit button to prevent double submission. Used in:
|
||||
|
||||
- ``snippets/forms.j2`` - Reply, edit, pay-what-you-can forms
|
||||
- ``snippets/create.j2`` - Thread creation form
|
||||
- ``snippets/snippets.j2`` - Watch, unwatch, lock, unlock, disable, enable, verify, approve, deny forms
|
||||
- ``snippets/search.j2`` - Search form
|
||||
- ``join-or-log-in.j2`` - Login form
|
||||
- ``setup-namespace.j2`` - Namespace setup/cancel forms
|
||||
- ``namespace-settings.j2`` - Settings forms
|
||||
- ``user-settings.j2`` - User settings form
|
||||
- ``user-watching.j2`` - Watching management form
|
||||
|
||||
Live Markdown Preview
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Pattern: ``onkeyup="previewAjax(...)"``
|
||||
|
||||
Triggers debounced Markdown preview on textarea input.
|
||||
|
||||
**snippets/forms.j2** (Line 21)
|
||||
Reply textarea with raw preview::
|
||||
|
||||
previewAjax('textarea-{{ node.id }}', 'preview-{{ node.id }}', true, {{ request.mathjax }})
|
||||
|
||||
**snippets/forms.j2** (Line 63)
|
||||
Edit textarea without raw preview::
|
||||
|
||||
previewAjax('edit-textarea-{{ node.id }}', 'node-data-{{ node.id }}', false, {{ request.mathjax }})
|
||||
|
||||
**snippets/create.j2** (Line 14)
|
||||
Thread creation textarea::
|
||||
|
||||
previewAjax('thread_data_textarea', 'preview', true, {{ request.mathjax }})
|
||||
|
||||
Toggle Functionality
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**base.j2** (Line 38)
|
||||
Namespace switcher menu::
|
||||
|
||||
onclick="toggle('my-namespaces-div', 'my-namespaces-link', '(switch)', '(switch)'); return false;"
|
||||
|
||||
**snippets/snippets.j2** (Line 88)
|
||||
Remark button - shows reply form and focuses textarea::
|
||||
|
||||
onclick="toggle('remark-box-{{ node.id }}', 'remark-link-{{ node.id }}', 'remark', 'hide'); document.getElementById('textarea-{{ node.id }}').focus(); return false;"
|
||||
|
||||
**snippets/snippets.j2** (Line 103)
|
||||
Collapse button - hides/shows child nodes::
|
||||
|
||||
onclick="toggle('node-children-{{ node.id }}', 'collapse-link-{{ node.id }}', 'expand [+]', 'collapse [-]');"
|
||||
|
||||
**snippets/snippets.j2** (Line 214)
|
||||
Edit button - shows edit form and focuses textarea::
|
||||
|
||||
onclick="toggle('edit-box-{{ node.id }}', 'edit-link-{{ node.id }}', 'edit', 'hide'); document.getElementById('edit-textarea-{{ node.id }}').focus(); return false;"
|
||||
|
||||
Alert Dismissal
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
**snippets/flash-alerts.j2** (Line 4)
|
||||
Click to dismiss alert::
|
||||
|
||||
onclick="this.style.display='none'"
|
||||
|
||||
Theme Preview
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
**user-settings.j2** (Lines 56-60)
|
||||
Radio buttons for theme mode::
|
||||
|
||||
onchange="previewTheme(this.value)"
|
||||
|
||||
**user-settings.j2** (Lines 108-127)
|
||||
Theme preview function - applies ``dark-mode`` class to HTML element.
|
||||
|
||||
|
||||
External Scripts
|
||||
----------------
|
||||
|
||||
snippets/javascript-includes.j2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**CSRF Token** (Line 6)
|
||||
Global variable for AJAX requests::
|
||||
|
||||
var csrf_token = "{{ request.session.get_csrf_token() }}";
|
||||
|
||||
**Google Analytics v4** (Lines 10-18)
|
||||
Conditional loading based on namespace configuration (gtag.js).
|
||||
|
||||
**MathJax** (Lines 20-27)
|
||||
Mathematical formula rendering. Loaded from CDN when enabled.
|
||||
|
||||
embed-iframe.txt.j2
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Embed script (Lines 8-42) that:
|
||||
|
||||
1. Captures parent page URL, title, and fragment
|
||||
2. Creates Remarkbox iframe with configuration
|
||||
3. Initializes iframe-resizer for responsive sizing
|
||||
|
||||
snippets/stripe.j2
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Stripe v3** (Line 57)
|
||||
Payment processing library from ``https://js.stripe.com/v3/``
|
||||
|
||||
**Payment form handling** (Lines 83-132)
|
||||
Stripe card element initialization, validation, and token creation.
|
||||
|
||||
|
||||
CSS Classes Managed by JavaScript
|
||||
---------------------------------
|
||||
|
||||
- ``toggle-open`` - Element is visible with open animation
|
||||
- ``toggle-closing`` - Element is animating closed
|
||||
- ``closing`` - Details element is animating closed
|
||||
- ``focused`` - URL fragment target highlighting
|
||||
- ``dark-mode`` - Dark theme applied to HTML element
|
||||
|
||||
|
||||
No-JavaScript Fallback
|
||||
----------------------
|
||||
|
||||
Remarkbox functions without JavaScript:
|
||||
|
||||
- Toggle links have ``href`` attributes pointing to dedicated pages
|
||||
(e.g., ``/{node_id}/edit``, ``/{node_id}/reply``)
|
||||
- Forms submit normally without AJAX
|
||||
- ``<details>`` elements work natively for preview toggle
|
||||
- Textareas remain fixed size (no auto-grow)
|
||||
- Voting requires JavaScript (AJAX-only)
|
||||
|
||||
|
||||
Removed Dependencies
|
||||
--------------------
|
||||
|
||||
The following were removed to reduce bundle size:
|
||||
|
||||
- **jQuery 2.1.3** (84KB) - Replaced with vanilla JS (fetch, addEventListener, querySelectorAll)
|
||||
- **Legacy Google Analytics** (ga.js) - Using gtag v4 instead
|
||||
- **IE8 polyfills** - IE8 is no longer supported
|
||||
|
|
@ -417,29 +417,106 @@ form.node-action {
|
|||
|
||||
.remark-box-div {
|
||||
display: none;
|
||||
/* this is needed to prevent "jumping" jquery bug. */
|
||||
overflow: hidden;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.edit-box-div {
|
||||
display: none;
|
||||
/* this is needed to prevent "jumping" jquery bug. */
|
||||
overflow: hidden;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
/* CSS animation when toggled open via JS */
|
||||
.remark-box-div.toggle-open,
|
||||
.edit-box-div.toggle-open {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
max-height: 1000px;
|
||||
animation: slideDown 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.remark-box-div.toggle-closing,
|
||||
.edit-box-div.toggle-closing {
|
||||
display: block;
|
||||
max-height: 1000px;
|
||||
animation: slideUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
from {
|
||||
max-height: 0;
|
||||
}
|
||||
to {
|
||||
max-height: 1000px;
|
||||
}
|
||||
}
|
||||
|
||||
.my-namespaces-div {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #ffffff;
|
||||
z-index: 1;
|
||||
/* this is needed to prevent "jumping" jquery bug. */
|
||||
overflow: hidden;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.my-namespaces-div.toggle-open {
|
||||
display: block;
|
||||
animation: slideDown 0.8s ease-out;
|
||||
}
|
||||
|
||||
/* Preview toggle with CSS animation */
|
||||
.preview-details {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.preview-details[open] > .preview {
|
||||
overflow: hidden;
|
||||
animation: slideDown 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
.preview-details.closing > .preview {
|
||||
overflow: hidden;
|
||||
max-height: 1000px;
|
||||
animation: slideUp 0.8s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
max-height: 1000px;
|
||||
}
|
||||
to {
|
||||
max-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.preview-toggle {
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.preview-toggle::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preview-toggle .when-open {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.preview-toggle .when-closed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preview-details:not([open]) .when-open {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preview-details:not([open]) .when-closed {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#remarkbox-footer {
|
||||
font-size: 0.8em;
|
||||
font-weight: bold;
|
||||
|
|
@ -475,9 +552,10 @@ form.node-action {
|
|||
}
|
||||
|
||||
.common-textarea {
|
||||
min-height: calc(2rem * var(--line-height));
|
||||
height: calc(5rem * var(--line-height));
|
||||
min-height: calc(3rem * var(--line-height));
|
||||
max-height: 400px;
|
||||
resize: vertical;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.monospace {
|
||||
|
|
|
|||
|
|
@ -2,92 +2,156 @@
|
|||
// previewTimer must live outside the functions.
|
||||
var previewTimer = null;
|
||||
|
||||
function previewAjax(textarea, div, show_raw = false, mathjax = false){
|
||||
function previewAjax(textarea, div, show_raw, mathjax) {
|
||||
// set div to raw textarea while waiting for remote Markdown rendering.
|
||||
if (show_raw) {
|
||||
// bust HTML tags like <script> to prevent running evil code.
|
||||
var busted_textarea = $('#'+textarea).val().replace(/&/g, '&').replace(/</g,'<');
|
||||
// $('#' + div).html('<div class="preview-raw-markdown">'+busted_textarea+'</div>');
|
||||
$('#' + div).html('<span class="preview-raw-markdown">'+busted_textarea+'</span>');
|
||||
// bust HTML tags like <script> to prevent running evil code.
|
||||
var el = document.getElementById(textarea);
|
||||
var busted_textarea = el.value.replace(/&/g, '&').replace(/</g, '<');
|
||||
document.getElementById(div).innerHTML = '<span class="preview-raw-markdown">' + busted_textarea + '</span>';
|
||||
}
|
||||
if (previewTimer) {
|
||||
clearTimeout(previewTimer);
|
||||
}
|
||||
previewTimer = setTimeout(
|
||||
function() { sendPreview( textarea, div, mathjax ); },
|
||||
800
|
||||
function() { sendPreview(textarea, div, mathjax); },
|
||||
800
|
||||
);
|
||||
}
|
||||
|
||||
function sendPreview(textarea, div, mathjax=false){
|
||||
$.ajaxSetup ({
|
||||
cache: false
|
||||
function sendPreview(textarea, div, mathjax) {
|
||||
var url = '/preview-post';
|
||||
var data = new FormData();
|
||||
data.append('data', document.getElementById(textarea).value);
|
||||
data.append('csrf_token', csrf_token);
|
||||
|
||||
fetch(url, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
})
|
||||
.then(function(response) { return response.text(); })
|
||||
.then(function(html) {
|
||||
document.getElementById(div).innerHTML = html;
|
||||
if (mathjax && typeof MathJax !== 'undefined') {
|
||||
setTimeout(function() {
|
||||
MathJax.Hub.Queue(["Typeset", MathJax.Hub, div]);
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// CSS-based toggle for smoother animations.
|
||||
function toggle(target, button, off_text, on_text) {
|
||||
if (typeof on_text === 'undefined') on_text = 'hide';
|
||||
var el = document.getElementById(target);
|
||||
var btn = document.getElementById(button);
|
||||
if (el.classList.contains('toggle-open')) {
|
||||
// Animate close, then update text
|
||||
el.classList.add('toggle-closing');
|
||||
setTimeout(function() {
|
||||
el.classList.remove('toggle-open');
|
||||
el.classList.remove('toggle-closing');
|
||||
btn.textContent = off_text;
|
||||
}, 800);
|
||||
} else {
|
||||
// Update text immediately when opening
|
||||
btn.textContent = on_text;
|
||||
el.classList.add('toggle-open');
|
||||
// Auto-grow any textareas that have content
|
||||
var textarea = el.querySelector('.common-textarea');
|
||||
if (textarea && textarea.value) {
|
||||
setTimeout(function() { autoGrow(textarea); }, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animate <details> close for preview-details elements.
|
||||
if (typeof document.addEventListener === 'function') {
|
||||
document.addEventListener('click', function(e) {
|
||||
var summary = e.target.closest('.preview-toggle');
|
||||
if (!summary) return;
|
||||
var details = summary.parentElement;
|
||||
if (!details || !details.classList.contains('preview-details')) return;
|
||||
if (details.open && !details.classList.contains('closing')) {
|
||||
e.preventDefault();
|
||||
details.classList.add('closing');
|
||||
setTimeout(function() {
|
||||
details.open = false;
|
||||
details.classList.remove('closing');
|
||||
}, 800);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
// Auto-grow textarea as content is added
|
||||
function autoGrow(el) {
|
||||
el.style.height = 'auto';
|
||||
var newHeight = Math.min(el.scrollHeight, 400);
|
||||
el.style.height = newHeight + 'px';
|
||||
}
|
||||
|
||||
// Initialize on DOM ready
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
|
||||
// Auto-grow textareas on input
|
||||
document.querySelectorAll('.common-textarea').forEach(function(textarea) {
|
||||
textarea.addEventListener('input', function() {
|
||||
autoGrow(this);
|
||||
});
|
||||
});
|
||||
|
||||
var url = '/preview-post';
|
||||
var params = { 'data' : $('#'+textarea).val(), 'csrf_token' : csrf_token };
|
||||
// Vote button handlers
|
||||
document.querySelectorAll('button.vote-up').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var post_id = this.closest('div').id;
|
||||
sendVote(post_id, 'up');
|
||||
});
|
||||
});
|
||||
|
||||
$( '#' + div ).load( url, params );
|
||||
document.querySelectorAll('button.vote-down').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
var post_id = this.closest('div').id;
|
||||
sendVote(post_id, 'down');
|
||||
});
|
||||
});
|
||||
|
||||
if (mathjax) {
|
||||
setTimeout(function(){
|
||||
// hack: block MathJax rerender for 50ms, give div chance to load.
|
||||
MathJax.Hub.Queue(["Typeset",MathJax.Hub,div]);
|
||||
// Fade in alert elements
|
||||
document.querySelectorAll('.alert').forEach(function(el) {
|
||||
setTimeout(function() {
|
||||
el.style.transition = 'opacity 2s';
|
||||
el.style.opacity = '1';
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Highlight fragment hash div if it exists
|
||||
if (window.location.hash) {
|
||||
var fragment = document.getElementById('node-data-' + window.location.hash.replace('#', ''));
|
||||
if (fragment) {
|
||||
fragment.classList.add('focused');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this toggles a dropdown.
|
||||
function toggle(target, button, off_text, on_text="hide"){
|
||||
if (!$('#' + target + ":visible").height()){
|
||||
$('#' + target).slideDown("slow");
|
||||
$('#' + button).text(on_text);
|
||||
}
|
||||
else {
|
||||
$('#' + target).slideUp("slow");
|
||||
$('#' + button).text(off_text);
|
||||
}
|
||||
}
|
||||
|
||||
$(document).ready( function() {
|
||||
|
||||
$('button.vote-up').click(
|
||||
function(){
|
||||
var post_id = $(this).closest('div').attr('id');
|
||||
sendVote( post_id, 'up' );
|
||||
}
|
||||
);
|
||||
|
||||
$('button.vote-down').click(
|
||||
function(){
|
||||
var post_id = $(this).closest('div').attr('id');
|
||||
sendVote( post_id, 'down' );
|
||||
}
|
||||
);
|
||||
|
||||
// make all elements with class alert fade into existance.
|
||||
$('.alert').delay(100).animate({"opacity": "1"}, 2000);
|
||||
|
||||
// highlight a fragment hash div if it exsits.
|
||||
if(window.location.hash) {
|
||||
// Fragment exists, so target the node's div.
|
||||
var fragment = '#node-data-' + window.location.hash.replace('#', '');
|
||||
$(fragment).addClass("focused");
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function sendVote( post_id, direction ){
|
||||
var url = '/vote-post';
|
||||
var params = { 'post_id' : post_id, 'direction' : direction, 'csrf_token' : csrf_token };
|
||||
function sendVote(post_id, direction) {
|
||||
var url = '/vote-post';
|
||||
var params = new URLSearchParams({
|
||||
'post_id': post_id,
|
||||
'direction': direction,
|
||||
'csrf_token': csrf_token
|
||||
});
|
||||
|
||||
$.getJSON( url, params, function( r ){
|
||||
if ( r['status'] ) {
|
||||
$('#'+post_id).children('b').html(r['vote_sum']);
|
||||
}
|
||||
else {
|
||||
$('#json').html(r['msg']);
|
||||
}
|
||||
}
|
||||
);
|
||||
fetch(url + '?' + params.toString())
|
||||
.then(function(response) { return response.json(); })
|
||||
.then(function(r) {
|
||||
if (r['status']) {
|
||||
var el = document.getElementById(post_id);
|
||||
var b = el.querySelector('b');
|
||||
if (b) b.innerHTML = r['vote_sum'];
|
||||
} else {
|
||||
var jsonEl = document.getElementById('json');
|
||||
if (jsonEl) jsonEl.innerHTML = r['msg'];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
// IE8 polyfils for iframeResizer.js
|
||||
|
||||
Array.prototype.forEach||(Array.prototype.forEach=function(a){"use strict";if(void 0===this||null===this||"function"!=typeof a)throw new TypeError;for(var b=Object(this),c=b.length>>>0,d=arguments.length>=2?arguments[1]:void 0,e=0;c>e;e++)e in b&&a.call(d,b[e],e,b)}),Function.prototype.bind||(Function.prototype.bind=function(a){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var b=Array.prototype.slice.call(arguments,1),c=this,d=function(){},e=function(){return c.apply(this instanceof d?this:a,b.concat(Array.prototype.slice.call(arguments)))};return d.prototype=this.prototype,e.prototype=new d,e}),Array.prototype.forEach||(Array.prototype.forEach=function(a,b){if(null===this)throw new TypeError(" this is null or not defined");if("function"!=typeof a)throw new TypeError(a+" is not a function");for(var c=Object(this),d=c.length>>>0,e=0;d>e;e++)e in c&&a.call(b,c[e],e,c)});
|
||||
//# sourceMappingURL=ie8.polyfils.map
|
||||
4
remarkbox/static/js/jquery-2.1.3.min.js
vendored
4
remarkbox/static/js/jquery-2.1.3.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -26,8 +26,8 @@
|
|||
{% set submit_button_value = 'save message' %}
|
||||
{% include 'submit.j2' %}
|
||||
|
||||
<details open>
|
||||
<summary class="action link" float="right">hide preview</summary>
|
||||
<details class="preview-details" open>
|
||||
<summary class="action link preview-toggle"><span class="when-open">hide preview ▲</span><span class="when-closed">show preview ▼</span></summary>
|
||||
<div id='preview-{{ node.id }}' name='preview' class='preview js-only'></div>
|
||||
</details>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
{% if 'google-analytics-id' in request.app %}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
|
||||
ga('create', '{{ request.app['google-analytics-id'] }}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
|
||||
</script>
|
||||
{% endif %}
|
||||
|
|
@ -5,7 +5,6 @@
|
|||
<!-- this is needed for async javascript requests -->
|
||||
<script>var csrf_token = "{{ request.session.get_csrf_token() | default("none", true) }}";</script>
|
||||
|
||||
<script src="/static/js/jquery-2.1.3.min.js"></script>
|
||||
<script src="/static/js/iframe-resizer/iframeResizer.contentWindow.min.js"></script>
|
||||
<script src="/static/js/custom.js"></script>
|
||||
{%- if request.mode == "basic" and request.namespace and request.namespace.name == request.domain and request.namespace.google_analytics_id %}
|
||||
|
|
@ -26,4 +25,3 @@
|
|||
src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js?config=TeX-MML-AM_CHTML,Safe">
|
||||
</script>
|
||||
{%- endif %}
|
||||
{%- include 'google-analytics.j2' %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue