diff --git a/.gitignore b/.gitignore
index f69273a08fe..e6172bed053 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ celerybeat.pid
chatconfig/config.json
# Sensitive environment files
+.npmrc
.env
*.pem
diff --git a/app/app/bundle_context.py b/app/app/bundle_context.py
new file mode 100644
index 00000000000..be87622a344
--- /dev/null
+++ b/app/app/bundle_context.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+"""Define additional context data to be passed to any request.
+
+Copyright (C) 2021 Gitcoin Core
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+
+"""
+
+from cacheops import cached_as
+from perftools.models import JSONStore
+
+@cached_as(JSONStore.objects.filter(view='bundleTags', key='bundleTags'), timeout=60)
+def templateTags():
+
+ # list any templateTags used inside bundle tags
+ return ['static']
+
+
+@cached_as(JSONStore.objects.filter(view='bundleContext', key='bundleContext'), timeout=60)
+def context():
+ context = {}
+
+ # return constructed context
+ return context
diff --git a/app/app/context.py b/app/app/context.py
index 828ac8b447a..8e6bab596af 100644
--- a/app/app/context.py
+++ b/app/app/context.py
@@ -26,6 +26,7 @@
from django.utils import timezone
import requests
+from app.bundle_context import context as bundleContext
from app.utils import get_location_from_ip
from cacheops import cached_as
from dashboard.models import Activity, Tip, UserAction
@@ -213,4 +214,8 @@ def preprocess(request):
context['unclaimed_tips'] = context['unclaimed_tips'].filter(network='mainnet').cache(timeout=60)
context['unclaimed_kudos'] = context['unclaimed_kudos'].filter(network='mainnet').cache(timeout=60)
+ # add bundleContext to context in dev
+ if settings.DEBUG:
+ context.update(bundleContext())
+
return context
diff --git a/app/app/templates/shared/tip_dependancies.html b/app/app/templates/shared/tip_dependancies.html
index fd209a76f74..f5a7b38d850 100644
--- a/app/app/templates/shared/tip_dependancies.html
+++ b/app/app/templates/shared/tip_dependancies.html
@@ -17,11 +17,12 @@
{% load static bundle %}
-
+
{% bundle merge_js file tip_dependancies %}
-
-
-
+
+
+
+
+
+
{% endbundle %}
-
-
diff --git a/app/assets/v2/js/base.js b/app/assets/v2/js/base.js
index 8ece965b75a..cc677d403ee 100644
--- a/app/assets/v2/js/base.js
+++ b/app/assets/v2/js/base.js
@@ -1,7 +1,7 @@
/* eslint-disable no-loop-func */
/* eslint-disable no-console */
/* eslint-disable nonblock-statement-body-position */
-$(document).ready(function() {
+document.addEventListener('DOMContentLoaded', function() {
$.fn.isInViewport = function() {
var elementTop = $(this).offset().top;
diff --git a/app/dashboard/management/commands/bundle.py b/app/dashboard/management/commands/bundle.py
index 2a297def1be..b32d6ba8cab 100644
--- a/app/dashboard/management/commands/bundle.py
+++ b/app/dashboard/management/commands/bundle.py
@@ -4,17 +4,29 @@
from django.conf import settings
from django.core.management.base import BaseCommand
+from django.template import Context, Template
from django.template.loaders.app_directories import get_app_template_dirs
from dashboard.templatetags.bundle import render
+from app.bundle_context import context, templateTags
-def rmdir(loc):
+
+def rmdir(loc, depth=1):
# drop both the bundled and the bundles before recreating
if os.path.exists(loc) and os.path.isdir(loc):
- print('- Deleting assets from: %s' % loc)
- shutil.rmtree(loc)
+ # list all dirs/paths in the loc
+ files = os.listdir(loc)
+
+ print('%s Deleting %s assets from: %s' % ('-' * depth, len(files), loc))
+ # delete files/dirs from the given loc leaving the loc dir intact
+ for path in files:
+ nextLoc = os.path.join(loc, path)
+ if os.path.isdir(nextLoc):
+ rmdir(nextLoc, depth+1)
+ else:
+ os.remove(nextLoc)
def rmdirs(loc, kind):
# base path of the assets
@@ -29,6 +41,11 @@ class Command(BaseCommand):
help = 'generates .js/.scss files from bundle template tags'
def handle(self, *args, **options):
+ """ This command will collect templates, render them with the bundleContext and run them through the bundle procedure"""
+
+ print('\nCollect template files from all apps:')
+
+ # get a list of all templates (rather than views to avoid segfault error that django-compressor was giving us on production)
template_dir_list = []
for template_dir in get_app_template_dirs('templates'):
if settings.BASE_DIR in template_dir:
@@ -41,29 +58,38 @@ def handle(self, *args, **options):
if ".html" in filename:
template_list.append(os.path.join(base_dir, filename))
+ print('\n- %s templates discovered' % len(template_list))
+
# using regex to grab the bundle tags content from html
block_pattern = re.compile(r'({%\sbundle(.|\n)*?(?<={%\sendbundle\s%}))')
open_pattern = re.compile(r'({%\s+bundle\s+(js|css|merge_js|merge_css)\s+?(file)?\s+?([^\s]*)?\s+?%})')
close_pattern = re.compile(r'({%\sendbundle\s%})')
- static_open_pattern = re.compile(r'({%\sstatic\s["|\'])')
- static_close_pattern = re.compile(r'(\s?%}(\"|\')?\s?\/?>)')
+
+ print('\nClear bundle directories:\n')
# remove the previously bundled files
for ext in ['js', 'scss', 'css']:
rmdirs('assets', ext)
rmdirs('static', ext)
- print('\nStart generating bundle files\n')
+ print('\nStart generating bundled assets (using app.bundleContext as context):\n')
# store unique entries for count
rendered = dict()
+ # get the tags and context from bundleContext
+ tags = templateTags()
+ bundleContext = context()
+ # load the bundleContext into a Context instance so that it can be fed to Template.render
+ bundleContext = Context(bundleContext)
+
+ # check every template for bundle tags
for template in template_list:
try:
- f = open(('%s' % template).replace('/', os.sep), 'r', encoding='utf8')
-
- t = f.read()
- if re.search(block_pattern, t) is not None:
+ # read the template file
+ t = open(('%s' % template).replace('/', os.sep), 'r', encoding='utf8').read()
+ # check for bundle tags
+ if re.search(block_pattern, t):
for m in re.finditer(block_pattern, t):
block = m.group(0)
details = re.search(open_pattern, block)
@@ -76,15 +102,22 @@ def handle(self, *args, **options):
block = re.sub(open_pattern, '', block)
block = re.sub(close_pattern, '', block)
- # clean static helper if we havent ran this through parse
- block = re.sub(static_open_pattern, '', block)
- block = re.sub(static_close_pattern, '>', block)
+ # add static helper to the block
+ block = '{% ' + 'load %s' % ' '.join(tags) + ' %}\n' + block
+
+ # create a template from the block
+ block = Template(block)
+
+ # render the template with bundleContext
+ block = block.render(bundleContext)
+
+ # clean static_url from the path (its not required but is included as legacy in most script/link inclusions)
+ block = block.replace(settings.STATIC_URL, "")
# render the template (producing a bundle file)
rendered[render(block, kind, 'file', name, True)] = True
-
except Exception as e:
print('-- X - failed to parse %s: %s' % (template, e))
pass
- print('\nGenerated %s bundle files' % len(rendered))
+ print('\n\n------------------- Generated %s bundled assets ------------------- \n\n' % len(rendered))
diff --git a/app/dashboard/templates/bounty/details.html b/app/dashboard/templates/bounty/details.html
index d9aab17a226..171a3ccc3ca 100644
--- a/app/dashboard/templates/bounty/details.html
+++ b/app/dashboard/templates/bounty/details.html
@@ -529,20 +529,23 @@