-
{{ reply.from }}
- on {{ reply.timestamp }}
- {% if reply.attachments %}
- | Attachments:
- {% for name, url in reply.attachments %}
-
{{name}}
- {% endfor %}
- {% endif %}
-
-
- {% if post_id == reply.id %}
-
+
+
{{ reply.from }}
+ on {{ reply.timestamp }}
+ {% if reply.attachments %}
+ | Attachments:
+ {% for name, url in reply.attachments %}
+
{{name}}
+ {% endfor %}
+ {% endif %}
+
+
+ {% if post_id == reply.id %}
+
+ {% else %}
+
+ {% endif %}
+
+ {% autoescape off %}{{ reply.text }}{% endautoescape %}
+
+
+
+ {% if reply.liked %}
+ +{{ reply.likes }}
+
+ |
+ Undo +1 Post |
{% else %}
-
+
+{{ reply.likes }}
+
+ |
+ +1 Post |
{% endif %}
-
- {% autoescape off %}{{ reply.text }}{% endautoescape %}
-
-
-
- {% if reply.liked %}
- +{{ reply.likes }}
-
- |
- Undo +1 Post |
- {% else %}
- +{{ reply.likes }}
-
- |
- +1 Post |
- {% endif %}
- Permalink
-
-
+ Permalink
+
+
{% endfor %}
-
+ {% endif %}
{% if user.is_authenticated %}
- {% if thread.member %}
-
-
-
-
- {% else %}
-
You must be a member to reply.
+ {% if post_status == "approved" %}
+ {% if thread.member %}
+
+
+
+
+ {% else %}
+
You must be a member to reply.
+ {% endif %}
{% endif %}
{% else %}
Login to reply
@@ -145,7 +166,13 @@
{{ thread.post.subject }}
{% block customjs %}
-
+
+
+
{% endblock %}
diff --git a/browser/views.py b/browser/views.py
index 7aeae3e0..dfe85930 100644
--- a/browser/views.py
+++ b/browser/views.py
@@ -86,10 +86,24 @@ def error(request):
def index(request):
homepage = "%s/home.html" % WEBSITE
if not request.user.is_authenticated:
+ # Hard coded info for demo, separated between tag and email example data
+ demo_tags = [{'color': '87CEEB', 'name': 'Courses', 'desc': 'New course annoucements and logistics related to EECS courses', 'num_p': '1' },
+ {'color': '008B8B', 'name': 'Jobs', 'desc': 'Any job related content such as internships, info sessions, career fair, etc.', 'num_p': '2' },
+ {'color': 'CD853F', 'name': 'UROPs', 'desc': 'UROP open positions announcements', 'num_p': '1' },
+ {'color': 'F08080', 'name': 'Talks', 'desc': 'Talks by professors, guests speakers, companies, etc.', 'num_p': '1' }
+ ]
+ demo_emails = [{ 'tags': [{ 'color': '87CEEB', 'name': 'Courses'}], 'subject': 'MAS.S65 Next generation devices for Nanoelectronics and Biotechnology' },
+ { 'tags': [{ 'color': 'F08080', 'name': 'Talks'}, { 'color': '008B8B', 'name': 'Jobs'}], 'subject': 'Tech Talk - Accessibility in Gaming hosted by Activison' },
+ { 'tags': [], 'subject': 'Participate in a Drinking Water Study & Win a Reusable Water Bottle' },
+ { 'tags': [{ 'color': 'CD853F', 'name': 'UROPs'}], 'subject': 'MIT Media Lab, Remote Fall UROP Opportunities in Lifelong Kindergarten Group' },
+ { 'tags': [{ 'color': '008B8B', 'name': 'Jobs'}], 'subject': 'Google Privacy Engineer Internship Opportunity' }
+ ]
return render(request,
homepage,
{'form': AuthenticationForm(),
- 'reg_form': RegistrationForm()})
+ 'reg_form': RegistrationForm(),
+ 'demo_info': { 'tags': demo_tags, 'emails': demo_emails},
+ 'settings': { 'tag_blocking_mode': True } })
else:
if WEBSITE == 'murmur':
return HttpResponseRedirect('/posts')
@@ -115,7 +129,7 @@ def post_list(request):
group_name = active_group['name']
tag_info = Tag.objects.filter(group=group).annotate(num_p=Count('tagthread')).order_by('-num_p')
-
+ logger.debug(tag_info)
if active_group['name'] == 'No Groups Yet':
return redirect('/group_list')
@@ -127,12 +141,10 @@ def post_list(request):
try:
threads = Thread.objects.filter(group=group)
threads = paginator(request.GET.get('page'), threads)
-
engine.main.list_posts_page(threads, group, res, user=user, format_datetime=False, return_replies=False, text_limit=250)
except Exception, e:
print e
res['code'] = msg_code['UNKNOWN_ERROR']
- logger.debug(res)
member_info = None
@@ -147,7 +159,7 @@ def post_list(request):
for tag in tag_info:
tag.muted = tag.mutetag_set.filter(user=user, group=group).exists()
tag.followed = not tag.muted
-
+ logger.debug(res)
return {'user': request.user, 'groups': groups, 'posts': res, 'active_group': active_group, "tag_info": tag_info,
"member_info": member_info, 'is_member': is_member}
else:
@@ -182,7 +194,11 @@ def thread(request):
return redirect('/404?e=thread')
group = thread.group
-
+ tag_info = Tag.objects.filter(group=group).annotate(num_p=Count('tagthread')).order_by('-num_p').values('name', 'color')
+ tag_lists = map(engine.main.encode_tags,tag_info)
+ tag_data = {'tags' : tag_lists}
+ thread_tags = {'tags' : map(engine.main.encode_tags, list(Tag.objects.filter(tagthread__thread=thread).values('name', 'color')))}
+
if request.user.is_authenticated:
user = get_object_or_404(UserProfile, email=request.user.email)
groups = Group.objects.filter(membergroup__member=user).values("name")
@@ -191,7 +207,6 @@ def thread(request):
member_group = MemberGroup.objects.filter(member=user, group=group)
is_member = member_group.exists()
-
modal_data = None
if is_member and (member_group[0].moderator or member_group[0].admin):
@@ -211,11 +226,12 @@ def thread(request):
elif WEBSITE == 'squadbox':
admin = MemberGroup.objects.get(group=group, admin=True)
thread_to = admin.member.email
-
+ # TODO: Remove post_status
return {'user': request.user, 'groups': groups, 'thread': res, 'thread_to' : thread_to,
- 'post_id': post_id, 'active_group': active_group, 'website' : WEBSITE,
- 'active_group_role' : role, 'groups_links' : groups_links, 'modal_data' : modal_data,
- 'mod_edit_wl_bl' : group.mod_edit_wl_bl}
+ 'post_id': post_id, 'post_status': "unapproved", 'tag_info': tag_lists,
+ 'thread_tags': thread_tags, 'tag_data': json.dumps(tag_data), 'active_group': active_group,
+ 'website' : WEBSITE, 'active_group_role' : role, 'groups_links' : groups_links,
+ 'modal_data' : modal_data, 'mod_edit_wl_bl' : group.mod_edit_wl_bl}
else:
if active_group['active']:
request.session['active_group'] = None
diff --git a/engine/main.py b/engine/main.py
index 922d6309..f6491fcd 100644
--- a/engine/main.py
+++ b/engine/main.py
@@ -1034,7 +1034,6 @@ def _create_tag(group, thread, name, color=None):
tagthread,_ = TagThread.objects.get_or_create(thread=thread, tag=t)
def _create_post(group, subject, tag_info, message_text, user, sender_addr, msg_id, verified, attachments=None, forwarding_list=None, post_status=None, sender_name=None):
-
try:
message_text = message_text.decode("utf-8")
except Exception, _:
@@ -1067,46 +1066,51 @@ def _create_post(group, subject, tag_info, message_text, user, sender_addr, msg_
poster_name=sender_name, verified_sender=verified, perspective_data=perspective_data)
p.save()
if WEBSITE == 'murmur':
+ group_members = MemberGroup.objects.filter(group=group)
tag_info = map(encode_tags, tag_info)
for tag in tag_info:
- _create_tag(group, thread, tag['name'], tag['color'])
+ _create_tag(group, thread, tag['name'], tag['color'])
tags = list(extract_hash_tags(message_text))
for tag in tags:
if tag.lower() != group.name:
_create_tag(group, thread, tag)
-
tag_objs = Tag.objects.filter(tagthread__thread=thread)
tags = list(tag_objs.values('name', 'color'))
- group_members = MemberGroup.objects.filter(group=group)
recipients = []
- for m in group_members:
- # print "create post", user.email
- print "donotsend", m.member.email
- dm = DoNotSendList.objects.filter(group=group, user=user, donotsend_user=m.member)
- blockingMode = getattr(m, 'tag_blocking_mode')
- if dm.exists():
- continue
- elif not m.no_emails and m.member.email != sender_addr:
- muted_tags = MuteTag.objects.filter(tag__in=tag_objs, group=group, user=m.member)
- if blockingMode:
- if not muted_tags.exists():
- recipients.append(m.member.email)
+ is_moderated = True # TODO: check if mailing list group is moderated
+ if is_moderated:
+ moderators = list(group_members.filter(moderator=True))
+ for mod in moderators:
+ recipients.append(mod.member.email)
+ else:
+ for m in group_members:
+ # print "create post", user.email
+ print "donotsend", m.member.email
+ dm = DoNotSendList.objects.filter(group=group, user=user, donotsend_user=m.member)
+ blockingMode = getattr(m, 'tag_blocking_mode')
+ if dm.exists():
+ continue
+ elif not m.no_emails and m.member.email != sender_addr:
+ muted_tags = MuteTag.objects.filter(tag__in=tag_objs, group=group, user=m.member)
+ if blockingMode:
+ if not muted_tags.exists():
+ recipients.append(m.member.email)
+ else:
+ if len(tags) > 0 and len(muted_tags) < len(tags):
+ recipients.append(m.member.email)
else:
- if len(tags) > 0 and len(muted_tags) < len(tags):
- recipients.append(m.member.email)
- else:
- recipients.append(m.member.email)
+ recipients.append(m.member.email)
- if user:
- recipients.append(user.email)
- f = Following(user=user, thread=thread)
- f.save()
+ if user:
+ recipients.append(user.email)
+ f = Following(user=user, thread=thread)
+ f.save()
elif WEBSITE == 'squadbox':
recipients = []
tags = None
tag_objs = None
-
+ logger.debug(recipients)
return p, thread, recipients, tags, tag_objs
def insert_post_web(group_name, subject, tag_info, message_text, user):
diff --git a/http_handler/static/css/murmur/base.css b/http_handler/static/css/murmur/base.css
index 51d125e2..662d6a45 100644
--- a/http_handler/static/css/murmur/base.css
+++ b/http_handler/static/css/murmur/base.css
@@ -3,6 +3,12 @@
@date: 08/11/2012
*/
+:root {
+ --primary-color: #447EAC;
+ --primary-light-color: #D7ECFA;
+ --secondary-color: #FF7477;
+}
+
body, html {
height: 100%;
}
@@ -11,61 +17,174 @@ body {
padding-top: 50px;
}
-hr{
- background-color: #447EAC;
- border: thin #447EAC solid;
+hr {
+ background-color: var(--primary-color);
+ border: thin var(--primary-color) solid;
+}
+
+.txt-primary {
+ color: var(--primary-color);
+}
+
+#navbar {
+ background: #F8F8F8;
+ filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.10));
}
#logo {
font-family: 'Pacifico', cursive;
- font-size: 22px;
+ font-size: 28px;
+ color: var(--primary-color);
}
#logo:hover {
- color: #000000
+ filter: contrast(150%) brightness(105%);
}
-#container-home {
- width: 800px;
- margin: 0 auto;
- background-color: #f8f8f8;
+#login-btn, #logout-btn, #cta-btn {
+ background: none;
+ font-weight: bold;
+ color: var(--primary-color);
+ border: 2px solid var(--primary-color);
+ transition: ease-out 0.4s;
+}
+
+#login-btn:hover,
+#logout-btn:hover,
+#login-btn:focus,
+#login-btn:focus {
+ box-shadow: inset 0 0 0 2em var(--primary-color);
+ color: #F8F8F8;
+}
+
+#intro {
+ display: flex;
+ flex-direction: column;
+ background: padding-box url(../third-party/images/home-intro-bg.svg); /* Background pattern from Toptal Subtle Patterns */
+ width: 100vw;
+ height: 100vh;
+ justify-content: center;
+}
+
+#intro-heading {
+ font-size: clamp(2em, 1em + 4vw, 4em);
+ font-family: 'Pacifico', cursive;
+ text-align: center;
+ color: var(--primary-color);
+ align-self: center;
+}
+
+#intro-subheading {
+ font-size: clamp(0.5em, 1em + 2vw, 2em);
+ font-weight: 500;
+ text-align: center;
+ color: #000000;
+ align-self: center;
+}
+
+#call-to-action {
+ text-decoration: none;
+ align-self: center;
}
-.title {
- color: #447EAC;
+#cta-btn {
+ background: none;
+ border: 2px solid var(--secondary-color);
+ padding: 1em 2em;
+ font-size: clamp(0.5em, 1em + 2vw, 1em);
+ color: var(--secondary-color);
font-weight: bold;
+ transition: ease-out 0.4s;
}
-#demo-desc {
- width: 1170px;
+#cta-btn:hover,
+#cta-btn:focus {
+ box-shadow: inset 16.5em 0 0 0 var(--secondary-color);
+ color: #F8F8F8;
+}
+
+#feature-desc {
+ width: 66vw;
margin: 0 auto;
- background-color: #f8f8f8;
}
-#container-demo {
- width: 1170px;
+.translate-down {
+ transform: translateY(64px);
+}
+
+.slide-in {
+ animation: slide-in 0.8s ease forwards;
+}
+
+.slide-in:nth-child(odd) {
+ animation-duration: 0.6s; /* staggered */
+}
+
+@keyframes slide-in {
+ to { transform: translateY(0); }
+}
+
+.feature-title {
+ display: inline-block;
+ font-size: clamp(0.5em, 1em + 2vw, 1.5em);
+ color: #FFF;
+ font-weight: 500;
+ border-radius: .25em;
+ text-align: left;
+}
+
+.feature-heading {
+ font-size: clamp(0.5em, 1em + 2vw, 1.5em);
+ color: var(--primary-color);
+ text-align: left;
+}
+
+.feature-subheading {
+ font-size: clamp(0.5em, 1em + 2vw, 1em);
+ font-weight: 500;
+ text-align: left;
+}
+
+#feature-demo {
+ width: 66vw;
margin: 0 auto;
- background-color: #f8f8f8;
+ background-color: #FFF;
justify-content: center;
+ box-shadow: 1px 1px 15px rgba(0, 0, 0, 0.25);
+ border-radius: 1em;
+ transform: translateY(64px);
}
.demo-settings {
- background-color: #D7ECFA;
+ background-color: var(--primary-light-color);
+ border-radius: 1em;
+ margin: clamp(0.5em, 0.5em + 2vw, 2em);
}
.demo-emails {
+ background-color: var(--primary-light-color);
list-style-type: none;
- padding: 0;
+ border-radius: 1em;
+ margin: clamp(0.5em, 0.5em + 2vw, 2em);
}
.sample-email {
font-weight: bold;
- background-color: #D7ECFA;
+ background-color: #FFF;
+ box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.25);
+ transition-property: transform, filter, opacity;
+ transition-duration: 0.3s, 0.3s, 0.5s;
+ transition-timing-function: ease-in, ease-in, ease-in-out;
+}
+
+.sample-email:not(.inactive):hover {
+ transform: scale(1.05);
+ filter: drop-shadow(0px 5px 2px rgba(0, 0, 0, 0.25));
}
.demo-sender {
font-weight: bold;
- color: #447EAC;
+ color: var(--primary-color);
}
#container-about {
@@ -88,18 +207,15 @@ hr{
}
}
-/* 1170px breakpoint to have demo section split into rows */
-@media only screen and (max-width: 1170px) {
- #container-demo {
+/* 1230px breakpoint to have demo section split into rows */
+@media only screen and (max-width: 1230px) {
+ #feature-demo {
flex-direction: column;
- max-width: 100%;
+ width: 80vw;
}
#demo-desc {
max-width: 100%;
}
- .demo-settings {
- max-width: 100%;
- }
}
/* 270px breakpoint to links under Publications section in rows */
@@ -110,6 +226,106 @@ hr{
}
}
+footer {
+ display: flex;
+ flex-direction: column;
+ background-color: #E9ECEF;
+ padding: clamp(1em, 1em + 2vw, 2em);
+}
+
+#footer-info {
+ font-size: clamp(0.75em, 1em + 2vw, 1em);
+}
+
+.footer-actions {
+ display: flex;
+ justify-content: center;
+}
+
+.footer-logo {
+ filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.10));
+}
+
+.footer-logo:hover {
+ filter: contrast(150%) brightness(105%);
+}
+
+.footer-nav {
+ display: flex;
+ align-items: center;
+}
+
+.footer-links {
+ display: flex;
+ list-style-type: none;
+}
+
+.footer-link {
+ font-size: clamp(1em, 1em + 2vw, 1.5em);
+ color: var(--primary-color);
+ text-decoration: none;
+ transition: ease-out 0.4s;
+}
+
+.footer-link:hover {
+ color: var(--secondary-color);
+ border-bottom: 5px solid var(--secondary-color);
+}
+
+.footer-join {
+ display: flex;
+ align-items: center;
+ filter: drop-shadow(0px 2px 2px rgba(0, 0, 0, 0.10));
+}
+
+.join-email {
+ width: clamp(6em, 12em + 2vw, 32em);
+ height: 3em;
+ border: none;
+ border-radius: 0.25em 0 0 0.25rem;
+}
+
+.join-email:focus {
+ outline: none;
+}
+
+.join-submit[value="Join"] {
+ height: 3em;
+ font-weight: 500;
+ background-color: var(--secondary-color);
+ border: none;
+ border-radius: 0 0.25rem 0.25rem 0;
+ outline: none;
+}
+
+.join-submit[value="Join"]:hover {
+ filter: contrast(200%);
+}
+
+/* 789px breakpoint to have demo section split into rows */
+@media only screen and (max-width: 789px) {
+ .footer-about {
+ text-align: center;
+ }
+ #footer-info {
+ font-size: clamp(0.25em, 0.25em + 2vw, 1em);
+ }
+ .footer-actions {
+ flex-direction: column;
+ align-items: center;
+ }
+ .footer-nav {
+ flex-direction: column;
+ }
+ .footer-links {
+ flex-direction: column;
+ font-size: clamp(0.25em, 0.25em + 2vw, 1em);
+ }
+ .footer-join {
+ margin-top: clamp(0.5em, 0.5em + 2vw, 1em);
+ }
+}
+
#about-left {
width: 100%;
}
@@ -138,19 +354,12 @@ hr{
color: #000000;
}
-.icon {
- height: 25px;
- position: relative;
- top: -5px;
- padding-right: 5px;
-}
-
.accounts {
margin:0px auto;
margin-top:30px;
padding:30px;
vertical-align:top;
- background-color: #D7ECFA;
+ background-color: var(--primary-light-color);
max-width:380px;
}
@@ -159,7 +368,7 @@ hr{
margin-top:30px;
padding:30px;
vertical-align:top;
- background-color: #D7ECFA;
+ background-color: var(--primary-light-color);
}
.group-container-posts {
@@ -167,7 +376,7 @@ hr{
margin-top:30px;
padding:8px;
vertical-align:top;
- background-color: #D7ECFA;
+ background-color: var(--primary-light-color);
}
#text-search-post {
@@ -185,7 +394,7 @@ hr{
}
.suggestion {
- border: #D7ECFA solid 1px;
+ border: var(--primary-light-color) solid 1px;
}
.tt-dropdown-menu {
@@ -194,11 +403,11 @@ hr{
}
.tt-dataset-posts {
- border: #447EAC solid 1px;
+ border: var(--primary-color) solid 1px;
}
.tt-dataset-groups {
- border: #447EAC solid 1px;
+ border: var(--primary-color) solid 1px;
}
.tt-dataset-posts a {
@@ -355,13 +564,13 @@ hr{
}
.post-container {
- background: #D7ECFA;
+ background: var(--primary-light-color);
padding: 5px 10px 5px 10px;
margin:0px;
}
.post-list-container {
- background: #D7ECFA;
+ background: var(--primary-light-color);
padding: 5px 0px 5px 0px;
margin:0px;
}
@@ -378,13 +587,13 @@ hr{
input, select{
padding:5px;
- border: #447EAC solid thin;
+ border: var(--primary-color) solid thin;
}
button, input[type="button"], input[type="submit"] {
background-color: #3D7AA6;
color: #FFF;
- border: #447EAC solid thin;
+ border: var(--primary-color) solid thin;
padding:5px 10px 5px 10px;
}
@@ -419,7 +628,7 @@ textarea {
}
.unread{
- background-color: #D7ECFA;
+ background-color: var(--primary-light-color);
color: #3D7AA6;
text-align:center;
width: 20px;
@@ -431,7 +640,7 @@ textarea {
}
.comment{
- background: #D7ECFA;
+ background: var(--primary-light-color);
padding: 20px;
margin-top:10px;
margin-bottom:10px;
@@ -567,7 +776,7 @@ textarea {
}
#members-table {
- background: #D7ECFA;
+ background: var(--primary-light-color);
}
@@ -584,11 +793,11 @@ blockquote {
padding-right: 10px;
}
-#image-attrib {
+/* #image-attrib {
position: relative;
bottom: 0;
right: 0;
-}
+} */
.paginate_disabled_previous, .paginate_disabled_next {
display: none;
@@ -615,7 +824,7 @@ blockquote {
box-sizing: content-box;
min-height: 2.3em;
background-color: #fff;
- border: 1px solid #447EAC;
+ border: 1px solid var(--primary-color);
flex-flow: row wrap;
list-style-type: none;
padding: 0;
@@ -623,7 +832,7 @@ blockquote {
}
#tag-input-list:focus-within {
- outline: 1px ridge #447EAC;
+ outline: 1px ridge var(--primary-color);
}
.tag-input-container {
@@ -635,7 +844,6 @@ blockquote {
.tag-input {
width: 100%;
- height: 100%;
border: none;
}
@@ -653,18 +861,25 @@ blockquote {
padding: 0;
border: 1px solid #d4d4d4;
}
+
+.tag-items-mod {
+ display: flex;
+ flex-wrap: wrap;
+ list-style-type: none;
+ padding: 0;
+ border: 1px solid #d4d4d4;
+}
-.tag-items .tag-item {
- display: inline-block;
+.tag-items .tag-item, .tag-items-mod .tag-item {
+ display: block;
+ box-sizing: content-box;
padding: 10px;
cursor: pointer;
background-color: #fff;
- border-bottom: 1px solid #d4d4d4;
- min-width: 100%;
width: auto;
}
-.tag-items .tag-item:hover {
+.tag-items .tag-item:hover, .tag-items-mod .tag-item:hover {
/*when hovering an titem:*/
background-color: #e9e9e9;
}
@@ -675,7 +890,7 @@ blockquote {
}
.tag-focus {
- outline: 4px solid #447EAC;
+ outline: 4px solid var(--primary-color);
}
.tag-subject-container {
@@ -690,6 +905,15 @@ blockquote {
flex-flow: column wrap;
}
+.post-tag-container-mod {
+ display: flex;
+ flex-flow: column wrap;
+}
+
+.mod-btn {
+ cursor: pointer;
+}
+
.tag-label-input {
display: inline-block;
padding: .2em .6em .2em;
@@ -730,7 +954,7 @@ blockquote {
}
.subject-input {
- border: 1px solid #447EAC;
+ border: 1px solid var(--primary-color);
height: 2.4em;
padding-top: 0.2em;
line-height: 1.5em;
@@ -738,7 +962,7 @@ blockquote {
}
.subject-input:focus-within {
- outline: 1px ridge #447EAC;
+ outline: 1px ridge var(--primary-color);
}
.tag {
diff --git a/http_handler/static/css/third-party/images/home-intro-bg.svg b/http_handler/static/css/third-party/images/home-intro-bg.svg
new file mode 100644
index 00000000..1aa387fc
--- /dev/null
+++ b/http_handler/static/css/third-party/images/home-intro-bg.svg
@@ -0,0 +1,10 @@
+
diff --git a/http_handler/static/images/favicon/android-chrome-192x192.png b/http_handler/static/images/favicon/android-chrome-192x192.png
index 38568d35..84c76b12 100644
Binary files a/http_handler/static/images/favicon/android-chrome-192x192.png and b/http_handler/static/images/favicon/android-chrome-192x192.png differ
diff --git a/http_handler/static/images/favicon/android-chrome-512x512.png b/http_handler/static/images/favicon/android-chrome-512x512.png
new file mode 100644
index 00000000..54d0d5b1
Binary files /dev/null and b/http_handler/static/images/favicon/android-chrome-512x512.png differ
diff --git a/http_handler/static/images/favicon/apple-touch-icon.png b/http_handler/static/images/favicon/apple-touch-icon.png
index 8303430f..31e22d31 100644
Binary files a/http_handler/static/images/favicon/apple-touch-icon.png and b/http_handler/static/images/favicon/apple-touch-icon.png differ
diff --git a/http_handler/static/images/favicon/favicon-16x16.png b/http_handler/static/images/favicon/favicon-16x16.png
index e022c458..f4bbb7f8 100644
Binary files a/http_handler/static/images/favicon/favicon-16x16.png and b/http_handler/static/images/favicon/favicon-16x16.png differ
diff --git a/http_handler/static/images/favicon/favicon-16x16.svg b/http_handler/static/images/favicon/favicon-16x16.svg
new file mode 100644
index 00000000..e3953229
--- /dev/null
+++ b/http_handler/static/images/favicon/favicon-16x16.svg
@@ -0,0 +1,6 @@
+
diff --git a/http_handler/static/images/favicon/favicon-194x194.png b/http_handler/static/images/favicon/favicon-194x194.png
deleted file mode 100644
index 65d09323..00000000
Binary files a/http_handler/static/images/favicon/favicon-194x194.png and /dev/null differ
diff --git a/http_handler/static/images/favicon/favicon-32x32.png b/http_handler/static/images/favicon/favicon-32x32.png
index 5693cc0c..0a31f149 100644
Binary files a/http_handler/static/images/favicon/favicon-32x32.png and b/http_handler/static/images/favicon/favicon-32x32.png differ
diff --git a/http_handler/static/images/favicon/favicon-32x32.svg b/http_handler/static/images/favicon/favicon-32x32.svg
new file mode 100644
index 00000000..75af2a1e
--- /dev/null
+++ b/http_handler/static/images/favicon/favicon-32x32.svg
@@ -0,0 +1,6 @@
+
diff --git a/http_handler/static/images/favicon/favicon-96x96.png b/http_handler/static/images/favicon/favicon-96x96.png
deleted file mode 100644
index 0315edde..00000000
Binary files a/http_handler/static/images/favicon/favicon-96x96.png and /dev/null differ
diff --git a/http_handler/static/images/favicon/favicon-96x96.svg b/http_handler/static/images/favicon/favicon-96x96.svg
new file mode 100644
index 00000000..db2c540e
--- /dev/null
+++ b/http_handler/static/images/favicon/favicon-96x96.svg
@@ -0,0 +1,6 @@
+
diff --git a/http_handler/static/images/favicon/favicon.ico b/http_handler/static/images/favicon/favicon.ico
index fa9d8c45..4c4130d3 100644
Binary files a/http_handler/static/images/favicon/favicon.ico and b/http_handler/static/images/favicon/favicon.ico differ
diff --git a/http_handler/static/images/murmurations/2115134127_9c074ca2b2_o.jpg b/http_handler/static/images/murmurations/2115134127_9c074ca2b2_o.jpg
deleted file mode 100644
index 9278f021..00000000
Binary files a/http_handler/static/images/murmurations/2115134127_9c074ca2b2_o.jpg and /dev/null differ
diff --git a/http_handler/static/images/murmurations/4227228_6bdfe3543e_o.jpg b/http_handler/static/images/murmurations/4227228_6bdfe3543e_o.jpg
deleted file mode 100644
index a5af2ffa..00000000
Binary files a/http_handler/static/images/murmurations/4227228_6bdfe3543e_o.jpg and /dev/null differ
diff --git a/http_handler/static/images/murmurations/4227243_a0587ccefe_o.jpg b/http_handler/static/images/murmurations/4227243_a0587ccefe_o.jpg
deleted file mode 100644
index 46ba54f0..00000000
Binary files a/http_handler/static/images/murmurations/4227243_a0587ccefe_o.jpg and /dev/null differ
diff --git a/http_handler/static/images/murmurations/4483398_dbd42c5993_o.jpg b/http_handler/static/images/murmurations/4483398_dbd42c5993_o.jpg
deleted file mode 100644
index 4f717c13..00000000
Binary files a/http_handler/static/images/murmurations/4483398_dbd42c5993_o.jpg and /dev/null differ
diff --git a/http_handler/static/images/murmurations/4483522_fbbf80a6ca_o.jpg b/http_handler/static/images/murmurations/4483522_fbbf80a6ca_o.jpg
deleted file mode 100644
index 3566e71b..00000000
Binary files a/http_handler/static/images/murmurations/4483522_fbbf80a6ca_o.jpg and /dev/null differ
diff --git a/http_handler/static/images/murmurations/4483546_9a1a9906e4_o.jpg b/http_handler/static/images/murmurations/4483546_9a1a9906e4_o.jpg
deleted file mode 100644
index 1093a6dd..00000000
Binary files a/http_handler/static/images/murmurations/4483546_9a1a9906e4_o.jpg and /dev/null differ
diff --git a/http_handler/static/images/murmurations/4483577_fec76ad00f_o.jpg b/http_handler/static/images/murmurations/4483577_fec76ad00f_o.jpg
deleted file mode 100644
index d13935eb..00000000
Binary files a/http_handler/static/images/murmurations/4483577_fec76ad00f_o.jpg and /dev/null differ
diff --git a/http_handler/static/images/murmurations/6507188799_8474f9775a_o.jpg b/http_handler/static/images/murmurations/6507188799_8474f9775a_o.jpg
deleted file mode 100644
index 597c85e4..00000000
Binary files a/http_handler/static/images/murmurations/6507188799_8474f9775a_o.jpg and /dev/null differ
diff --git a/http_handler/static/javascript/murmur/create_post.js b/http_handler/static/javascript/murmur/create_post.js
index 88e9ebb3..1e88dae7 100644
--- a/http_handler/static/javascript/murmur/create_post.js
+++ b/http_handler/static/javascript/murmur/create_post.js
@@ -1,3 +1,5 @@
+import { autocomplete, getTagItems, removeActiveTags, closeAllLists, handleTagInputKeys } from './modules/tag_input.js';
+
$(document).ready(function(){
let editor = CKEDITOR.replace( 'new-post-text', {
@@ -6,32 +8,38 @@ $(document).ready(function(){
CKEDITOR.instances['new-post-text'].on('contentDom', function() {
this.document.on('click', function(event){
- closeAllLists();
+ tagInput.currentAutocompleteFocus = -1;
+ closeAllLists(null, tagInput);
});
});
+
+ let userName = $.trim($('#user_email').text());
+ let post = $("#btn-post");
+ let subjectInput = {
+ 'elem': $("#new-post-subject").get(0),
+ 'height': 38,
+ }
+ let tagInfo = [];
+ const tagInput = {
+ context: "autocomplete",
+ container: $(".tag-input-container").get(0),
+ elem: $("#tag-input").get(0),
+ tags: django_tag_data["tags"],
+ list: $("#tag-input-list").get(0),
+ set: new Set(), // used to more efficiently determine if a certain tag exists
+ currentAutocompleteFocus: -1,
+ currentTagFocus: -1 // negative index of tag input item currently focused ex: [tag1,tag2,tag3,{tagInput}], {} = focused
+ }
- const userName = $.trim($('#user_email').text());
- tagInputContainer = $(".tag-input-container").get(0);
- tagInput = $("#tag-input").get(0);
- post = $("#btn-post");
- subjectInput = $("#new-post-subject").get(0);
- subjectInputHeight = 38;
- tags = django_tag_data["tags"];
- tagInputList = $("#tag-input-list").get(0);
- tagInputTagSet = new Set();
- tagInfo = [];
- currentAutocompleteFocus = -1;
- currentTagFocus = -1; // negative index of tag input item currently focused ex: [tag1,tag2,tag3,{tagInput}], {} = focused
-
- insert_post =
+ const insert_post =
function(params){
- if (tagInputTagSet.size == 0 && tagInput.value.length > 0) {
+ if (tagInput.set.size == 0 && tagInput.elem.value.length > 0) {
alert("Please press enter/tab after tag in tag input to add/create tag or remove any input if no tags desired.");
} else {
let subjectText = $("#new-post-subject").val();
- tagInputTagSet.forEach((tagName) => {
+ tagInput.set.forEach((tagName) => {
let tagElement = document.getElementById(tagName+"-tag-input");
- tagColor = tagElement.getAttribute("data-tagColor");
+ let tagColor = tagElement.getAttribute("data-tagColor");
subjectText = "[" + tagName + "]" + subjectText.substr(0);
tagInfo.push({"name": tagName, "color": tagColor});
});
@@ -53,22 +61,21 @@ $(document).ready(function(){
);
}
};
-
bind_buttons();
/* Listener for tag input box to autocomplete once it clicked and focused on */
- tagInput.addEventListener("focus", (event) => {
- currentTagFocus = -1;
- removeActiveTags(getTagItems());
- autocomplete();
+ tagInput.elem.addEventListener("focus", (event) => {
+ tagInput.currentTagFocus = -1;
+ removeActiveTags(getTagItems(tagInput.container));
+ autocomplete(tagInput);
});
/* Listener for tag input box to handle key press actions (e.g. navigating autocomplete tags, navigating added tags) */
- tagInput.addEventListener("keydown", handleTagInputKeys);
+ tagInput.elem.addEventListener("keydown", handleTagInputKeys.bind(tagInput));
/* Listener for subject line to resize according to the size of the text */
- subjectInput.addEventListener("input", resizeInput, false);
+ subjectInput.elem.addEventListener("input", resizeSubjectInput.bind(subjectInput), false);
function bind_buttons() {
post.unbind("click");
@@ -99,277 +106,14 @@ function notify(res, on_success){
}
}
-
-// AUTOCOMPLETE
-function autocomplete() {
- displayTags(tagInput.value); // Displays all tags when input string is empty (i.e. "")
- addActiveAutocomplete([]); // Initially no autocomplete tag item is active/highlighted
-
- tagInput.addEventListener("input", function(e) { // Listen to user input changes
- if (this.value.length === 0) currentAutocompleteFocus = -1;
- else currentAutocompleteFocus = 0;
- displayTags(this.value);
- let autocompleteItems = getAutocompleteItems(this.id);
- tagItems = getTagItems();
- addActiveAutocomplete(autocompleteItems);
- addActiveTags(tagItems);
- });
-
- // Close lists when someone clicks out of input
- $(document).click((e) => {
- currentAutocompleteFocus = -1;
- closeAllLists(e.target);
- });
-}
-
-function displayTags(val=null) {
- closeAllLists();
- // Create container for list items and add to parent container
- let autocompleteList = document.createElement("UL");
- autocompleteList.setAttribute("id", tagInput.id + "autocomplete-list");
- autocompleteList.setAttribute("class", "tag-items");
- tagInput.parentNode.appendChild(autocompleteList);
- for (let i = 0; i < tags.length; i++) {
- if (!val || tags[i]['name'].substr(0, val.length).toUpperCase() == val.toUpperCase()) { // Check if input value matches each word
- if (!tagInputTagSet.has(tags[i]['name'])) { // only autocomplete tags not already added
- // Add autocomplete item container to list
- autocompleteItem = document.createElement("LI");
- autocompleteItem.setAttribute("data-tagName", tags[i]['name']);
- autocompleteItem.setAttribute("data-tagColor", tags[i]['color']);
- autocompleteItem.setAttribute("class", "tag-item");
-
- // Add tag to item container
- tagItem = document.createElement("DIV");
- tagItem.setAttribute("class", "tag-label-autocomplete");
- tagItem.setAttribute("style", "background-color: #" + tags[i]['color'] + ";");
- tagItem.innerHTML = tags[i]['name'];
-
- autocompleteItem.appendChild(tagItem);
-
- autocompleteItem.addEventListener("click", function(e) { // Sets input to selected list item
- let tagName = this.getAttribute("data-tagName");
- tagColor = this.getAttribute("data-tagColor");
- tagInput.value = "";
- addTagToInput(tagName, tagColor);
- });
- autocompleteList.appendChild(autocompleteItem);
- }
- }
- }
-}
-
-function addTagToInput(tagName, tagColor) {
- // Add tag label to tag input list
- let tagInputItem = document.createElement("LI");
- tagInputItem.setAttribute("id", tagName + "-tag-input")
- tagInputItem.setAttribute("data-tagName",tagName);
- tagInputItem.setAttribute("data-tagColor",tagColor);
- tagInputItem.setAttribute("class", "tag-label-input")
- tagInputItem.setAttribute("style", "background-color: #" + tagColor + ";");
- tagInputItem.setAttribute("tabindex", 0);
- tagInputItem.innerHTML = tagName;
- tagInputItem.addEventListener("keydown", handleTagInputKeys);
- // Click event for focusing on tags in input
- tagInputItem.addEventListener("click", function () {
- let tagItems = getTagItems(); // [tags] + [inputElement]
- // ex: tagItems = [tag1,tag2,tag3,input] => target: tag3, index: 2 (-2) => -2 (tagFocus) = 2 (indexOf) - 4 (tagItems.length) as desired
- currentTagFocus = tagItems.indexOf(this) - tagItems.length;
- addActiveTags(tagItems);
- });
-
- // Add delete tag to tag label
- let tagInputDeleteBtn = createDeleteTagBtn(tagName, "input");
- tagInputItem.appendChild(tagInputDeleteBtn);
-
- tagInputList.insertBefore(tagInputItem, tagInputList.children[tagInputList.children.length-1]);
- tagInputTagSet.add(tagName);
- closeAllLists();
-}
-
-// Generates random colors that valid and are not pure white
-function generateRandomColor() {
- let randomColor = Math.floor(Math.random()*16777215).toString(16);
- if(randomColor.length != 6 || randomColor == "ffffff"){ // In any case, the color code is invalid or pure white
- randomColor = generateRandomColor();
- }
- return randomColor;
-}
-
-
-// Create delete tag button on tag labels in tag input
-function createDeleteTagBtn(tagName) {
- // Add delete tag button to tag label
- tagInputDeleteBtn = document.createElement("SPAN");
- tagInputDeleteBtn.setAttribute("data-tagName",tagName);
- tagInputDeleteBtn.setAttribute("class","tag-delete-btn ml-1");
- tagInputDeleteBtn.innerHTML = "x";
-
- tagInputDeleteBtn.addEventListener("click", function(e) {
- deleteTag(this.getAttribute("data-tagName"));
- // Prevents event propagation up DOM tree so parent's (tag container) click event doesn't occur
- e.stopPropagation();
- tagInputTagSet.delete(tagName); // Delete tags from tag input set
- tagInput.focus();
- });
-
- return tagInputDeleteBtn;
-}
-
-// Deletes the tag label associated to delete button
-function deleteTag(tagName) {
- let tag = document.getElementById(tagName + "-tag-input");
- tag.remove()
-}
-
-function resizeInput() {
- if (subjectInput.value == '') {
- subjectInput.setAttribute("style", "height:" + subjectInputHeight + "px;overflow-y:hidden;");
+// Resizes the subject input element
+function resizeSubjectInput() {
+ if (this.elem.value == '') {
+ this.elem.setAttribute("style", "height:" + this.height + "px;overflow-y:hidden;");
} else {
- subjectInput.setAttribute("style", "height:" + (subjectInput.scrollHeight) + "px;overflow-y:hidden;");
- subjectInput.style.height = "auto";
- subjectInput.style.height = (subjectInput.scrollHeight) + "px";
- }
-}
-
-// Gets the current autcomplete items
-function getAutocompleteItems(inputId) {
- let items = document.getElementById(inputId + "autocomplete-list");
- if (items) items = items.getElementsByTagName("LI");
- else items = []
- return items
-}
-
-// Gets the current tag items
-function getTagItems() {
- let items = document.getElementById("tag-input-list");
- if (items) {
- items = [...items.getElementsByClassName("tag-label-input")];
- if (items.length > 0) items.push(tagInputContainer);
- } else items = [];
- return items
-}
-
-// Makes item active in autocomplete dropdown
-function addActiveAutocomplete(items) {
- if (items.length === 0 || (currentAutocompleteFocus == -1 && items.length === 0)) return false;
- removeActiveAutocomplete(items);
-
- if (currentAutocompleteFocus >= items.length) currentAutocompleteFocus = 0;
- if (currentAutocompleteFocus < 0) currentAutocompleteFocus = (items.length - 1);
-
- items[currentAutocompleteFocus].setAttribute("id","autocomplete-active");
-}
-
-// Remove active class from autocomplete items
-function removeActiveAutocomplete(items) {
- for (var i = 0; i < items.length; i++) {
- items[i].removeAttribute("id");
- }
-}
-
-// Makes item active in added tagged
-function addActiveTags(items) {
- removeActiveTags(items);
- if (currentTagFocus >= -1) { // focus input from tags to (typing) input
- currentTagFocus = -1;
- $("#tag-input").focus();
- } else { // blur input (typing) in tag input if on tag and not entering input
- $("#tag-input").blur();
- closeAllLists();
- }
- if (items.length === 0 || currentTagFocus === -1) return false;
-
- if (Math.abs(currentTagFocus) >= items.length) currentTagFocus = -items.length;
- items[items.length+currentTagFocus].focus();
- items[items.length+currentTagFocus].classList.add("tag-focus");
-}
-
-// Remove active class from tag items
-function removeActiveTags(items) {
- for (var i = 0; i < items.length; i++) {
- items[i].blur();
- items[i].classList.remove("tag-focus");
- }
-}
-
-// Handles key presses for the tag input
-function handleTagInputKeys(e) {
- let autocompleteItems = getAutocompleteItems(tagInput.id);
- tagItems = getTagItems();
- if (e.keyCode == 40) { // DOWN arrow key
- e.preventDefault();
- currentAutocompleteFocus++;
- addActiveAutocomplete(autocompleteItems);
- } else if (e.keyCode == 38) { // UP arrow key
- e.preventDefault();
- currentAutocompleteFocus--;
- addActiveAutocomplete(autocompleteItems);
- } else if (e.keyCode == 37) { // LEFT arrow key
- if (tagItems.length > 0 && tagInput.value.length === 0) {
- e.preventDefault();
- currentTagFocus--;
- addActiveTags(tagItems);
- }
- } else if (e.keyCode == 39) { // RIGHT arrow key
- if (tagItems.length > 0 && tagInput.value.length === 0) {
- e.preventDefault();
- currentTagFocus++;
- addActiveTags(tagItems);
- }
- } else if(e.keyCode == 8) { // DELETE arrow key
- if (tagItems.length > 0 && currentTagFocus < -1) {
- e.preventDefault();
- deleteSelectedTag(tagItems);
- } else if (currentTagFocus === -1 && tagInput.value.length < 1) {
- currentTagFocus--;
- addActiveTags(tagItems);
- }
- } else if (e.keyCode == 13) { // ENTER key simulates click on list item
- e.preventDefault();
- if (autocompleteItems.length > 0 && currentAutocompleteFocus > -1) {
- if (autocompleteItems) autocompleteItems[currentAutocompleteFocus].click();
- } else if (tagInput.value.length > 0 && autocompleteItems.length == 0) { // check if no autocomplete suggestions -> meaning new tag creation
- let newTagName = tagInput.value;
- newTagColor = generateRandomColor();
- tagInput.value = "";
- addTagToInput(newTagName, newTagColor);
- }
- } else if (e.keyCode == 9) { // TAB or SHIFT key simulates click on list item
- removeActiveTags(tagItems);
- currentTagFocus = -1;
- if (currentAutocompleteFocus > -1) e.preventDefault(); // If no input for adding tag, then allow default tagging to navigate
- if (autocompleteItems.length > 0 && currentAutocompleteFocus > -1) {
- if (autocompleteItems) autocompleteItems[currentAutocompleteFocus].click();
- } else if (tagInput.value.length > 0 && autocompleteItems.length == 0) { // check if no autocomplete suggestions -> meaning new tag creation
- let newTagName = tagInput.value;
- newTagColor = generateRandomColor();
- tagInput.value = "";
- addTagToInput(newTagName, newTagColor);
- }
- currentAutocompleteFocus = -1;
- closeAllLists();
- }
-
- function deleteSelectedTag(tagItems) {
- removeActiveTags(tagItems);
- if (Math.abs(currentTagFocus) > tagItems.length) currentTagFocus = -tagItems.length; // handles index beyond first tag elem
- // ex: tagItems = [tag1,tag2,tag3,input] => target: tag3, index: 2 (-2) => 4 (tagItems.length) + -2 (tagFocus) = 2 as desired
- let tag = tagItems[tagItems.length+currentTagFocus];
- tag.remove();
- tagInputTagSet.delete(tag.getAttribute("data-tagName"));
- tagItems.splice(tagItems.length+currentTagFocus, 1); // recountruct tag input items after removal
- if (tagItems.length === 1) currentTagFocus = -1; // if only one item left, most be tag input elem which needs tagFocus = -1
- addActiveTags(tagItems); // focus next item in the list
- }
-}
-
-// Closes all lists except for selected list
-function closeAllLists(element) {
- let itemsToClose = $(".tag-items");
- for (let i = 0; i < itemsToClose.length; i++) {
- if (element != itemsToClose[i] && element != tagInput) {
- itemsToClose[i].parentNode.removeChild(itemsToClose[i]);
- }
+ this.elem.setAttribute("style", "height:" + (this.elem.scrollHeight) + "px;overflow-y:hidden;");
+ this.elem.style.height = "auto";
+ this.elem.style.height = (this.elem.scrollHeight) + "px";
}
}
diff --git a/http_handler/static/javascript/murmur/demo.js b/http_handler/static/javascript/murmur/demo.js
index a5b44d92..ae169afe 100644
--- a/http_handler/static/javascript/murmur/demo.js
+++ b/http_handler/static/javascript/murmur/demo.js
@@ -1,76 +1,38 @@
-$(document).ready(function(){
- let selectRows = $(".my_row");
- modeInput = $('input[name="tag-mode"]');
- selectTags = $('img[data-type="tag-select"]'); // select elements (block/check icons)
- sampleEmails = $(".sample-email");
- tags = $(".tag").toArray().map(e => e.innerHTML);
- selectTagsSet = new Set(selectTags.toArray());
- followedTags = new Set();
- mutedTags = new Set();
- swapping = false;
- tag_demo_table = $("#tag-demo-table").DataTable({
- "columns": [ { 'orderable': false}, null, null, null],
- "order": [[1, "asc"]],
- responsive: {
- details: {
- type: 'column',
- target: 'tr'
- }
- },
- searching: false,
- paginate: false,
- info: false,
- });
+import { addRowSelect, handleTagChanges, handleModeChanges } from './modules/tag_subscription.js';
- console.log(tags);
-
- // Click listener for the whole row to be clickable and toggle blocking/subscribing
- selectRows.each((index, elem) => {
- elem.addEventListener("click", (e) => {
- if (!selectTagsSet.has(e.target)) elem.firstElementChild.firstElementChild.click()
- });
+$(document).ready(function(){
+ const selectRows = $(".my_row");
+ let modeInput = $('input[name="tag-mode"]'),
+ selectedTags = $('img[data-type="tag-select"]'), // select elements (block/check icons)
+ tags = $(".tag").toArray().map(e => e.innerHTML),
+ selectedTagsSet = new Set(selectedTags.toArray()),
+ followedTags = new Set(),
+ mutedTags = new Set(),
+ swapping = false,
+ tag_demo_table = $("#tag-subscription-table").DataTable({
+ "columns": [ { 'orderable': false}, null, null, null],
+ "order": [[1, "asc"]],
+ responsive: {
+ details: {
+ type: 'column',
+ target: 'tr'
+ }
+ },
+ searching: false,
+ paginate: false,
+ info: false,
});
- // Select column with block and checkmark icons to select rows
- selectTags.each((index, elem) => {
- elem.addEventListener("click", function() {
- const mode = $('input[name="tag-mode"]:checked').val();
- const tag = elem.parentNode.nextElementSibling.firstElementChild;
- elem.toggleAttribute("checked");
- elem.classList.toggle("inactive");
-
- if (!swapping) {
- const isSelected = elem.hasAttribute("checked")
- if (mode == "block-mode") {
- if (isSelected) mutedTags.add(tag.innerHTML);
- else mutedTags.delete(tag.innerHTML);
- } else if (mode == "subscribe-mode") {
- if (isSelected) mutedTags.delete(tag.innerHTML);
- else mutedTags.add(tag.innerHTML);
- }
- }
- swapping = false;
- updateEmails();
- })
- });
-
- // Toggles visibility of tags based on tag mode change
- modeInput.change(function() {
- const mode = $('input[name="tag-mode"]:checked').val();
- selectTags.each((index, elem) => {
- if (mode == "block-mode") {
- elem.setAttribute("src", "/static/css/third-party/images/block.svg");
- } else if (mode == "subscribe-mode") {
- elem.setAttribute("src", "/static/css/third-party/images/check.svg");
- }
- swapping = true;
- elem.click()
- });
- });
+ // Handle actions to demo table
+ addRowSelect(selectRows, selectedTagsSet);
+ handleTagChanges(selectedTags, tags, mutedTags, followedTags, swapping, updateEmails);
+ handleModeChanges(modeInput, selectedTags, swapping);
});
-function updateEmails() {
+// Changes inbox emails in demo based on changes to tag subscription preferences
+function updateEmails(tags, mutedTags, followedTags) {
const mode = $('input[name="tag-mode"]:checked').val();
+ const sampleEmails = $(".sample-email");
if (mode == "block-mode") {
sampleEmails.each(function() {
const emailTags = $(this).children(".label2").toArray().map(x => x.innerHTML);
@@ -85,7 +47,7 @@ function updateEmails() {
followedTags = new Set(tags.filter(x => !mutedTags.has(x)));
sampleEmails.each(function() {
const emailTags = $(this).children(".label2").toArray().map(x => x.innerHTML);
- desiredTags = emailTags.filter(x => followedTags.has(x));
+ const desiredTags = emailTags.filter(x => followedTags.has(x));
if (emailTags.length === 0 || desiredTags.length === 0) {
this.classList.add("inactive");
} else {
diff --git a/http_handler/static/javascript/murmur/home.js b/http_handler/static/javascript/murmur/home.js
index bdca11eb..1c9293c3 100644
--- a/http_handler/static/javascript/murmur/home.js
+++ b/http_handler/static/javascript/murmur/home.js
@@ -1,44 +1,32 @@
$(document).ready(function(){
-
- var now = 0;
- var array = [
- "/static/images/murmurations/6507188799_8474f9775a_o.jpg",
- "/static/images/murmurations/2115134127_9c074ca2b2_o.jpg",
- "/static/images/murmurations/4227228_6bdfe3543e_o.jpg",
- "/static/images/murmurations/4227243_a0587ccefe_o.jpg",
- "/static/images/murmurations/4483577_fec76ad00f_o.jpg",
- "/static/images/murmurations/4483546_9a1a9906e4_o.jpg",
- "/static/images/murmurations/4483522_fbbf80a6ca_o.jpg",
- "/static/images/murmurations/4483398_dbd42c5993_o.jpg"
- ];
-
- var about = ["
Image by
Karen Fletcher",
- "
Image by
Paul",
- "
Image by
steve mcnicholas",
- "
Image by
steve mcnicholas",
- "
Image by
steve mcnicholas",
- "
Image by
steve mcnicholas",
- "
Image by
steve mcnicholas",
- "
Image by
steve mcnicholas",
- ]
-
- function getRandomInt(min, max) {
- return Math.floor(Math.random() * (max - min + 1)) + min;
- }
-
- var val = getRandomInt(0,7);
- $('body').css({'background-image': 'url("' + array[val] + '")'});
- $('#image-attrib').html(about[val]);
+ console.log("HOME");
- if ($('.home-left').length > 0) {
- window.setInterval(function() {
- var val = getRandomInt(0,7);
- $('body').css({'background-image': 'url("' + array[val] + '")'});
- $('#image-attrib').html(about[val]);
- }, 12000);
+ // Options for Intersection Observer (using viewport as root)
+ let options = {
+ root: null,
+ rootMargin: "0px",
+ threshold: 0.25,
}
+ // Callback for when targets intersect with observer
+ let slideIn = (entries, observer) => {
+ entries.forEach(entry => {
+ if (entry.isIntersecting) {
+ let elem = entry.target;
+
+ if (entry.intersectionRatio >= 0) {
+ elem.classList.add("slide-in");
+ }
+ }
+ });
+ };
+ // Set-up for observer and targets
+ let observer = new IntersectionObserver(slideIn, options);
+ let descTarget = document.querySelector("#feature-desc");
+ let demoTarget = document.querySelector("#feature-demo");
+ observer.observe(descTarget);
+ observer.observe(demoTarget);
});
diff --git a/http_handler/static/javascript/murmur/modules/tag_input.js b/http_handler/static/javascript/murmur/modules/tag_input.js
new file mode 100644
index 00000000..863e6340
--- /dev/null
+++ b/http_handler/static/javascript/murmur/modules/tag_input.js
@@ -0,0 +1,311 @@
+// AUTOCOMPLETE
+function autocomplete(tagInput) {
+ /* Listener for tag input box to handle key press actions (e.g. navigating autocomplete tags, navigating added tags) */
+ displayTags(tagInput.elem.value, tagInput); // Displays all tags when input string is empty (i.e. "")
+ addActiveListTag([], tagInput.currentAutocompleteFocus); // Initially no autocomplete tag item is active/highlighted
+
+ let doc = $(document).get(0);
+ tagInput.elem.addEventListener("input", function(e) { // Listen to user input changes
+ if (this.value.length === 0) tagInput.currentAutocompleteFocus = -1;
+ else tagInput.currentAutocompleteFocus = 0;
+ displayTags(this.value, tagInput);
+ let tagListItems = geTagListItems(this.id, tagInput),
+ tagItems = getTagItems(tagInput.container);
+ addActiveListTag(tagListItems, tagInput);
+ addActiveTags(tagItems, tagInput);
+ });
+
+ if (tagInput.context !== "moderation") {
+ // Close lists when someone clicks out of input
+ doc.addEventListener("click", (e) => {
+ if (e.target != tagInput.elem) {
+ tagInput.currentAutocompleteFocus = -1;
+ closeAllLists(null, tagInput);
+ }
+ });
+ }
+}
+
+// Enclosed functions to pass down the variables from autocomplete call
+function displayTags(val=null, tagInput) {
+ closeAllLists(null, tagInput);
+ // Create container for list items and add to parent container
+ let doc = $(document).get(0);
+ let tagList = doc.createElement("UL");
+ if (tagInput.context !== "moderation") {
+ tagList.setAttribute("id", tagInput.elem.id + "-autocomplete-list");
+ tagList.setAttribute("class", "tag-items");
+ tagInput.elem.parentNode.appendChild(tagList);
+ } else {
+ tagList.setAttribute("id", tagInput.elem.id + "-mod-list");
+ tagList.setAttribute("class", "tag-items-mod");
+ tagInput.elem.parentNode.parentNode.parentNode.appendChild(tagList);
+ }
+ for (let i = 0; i < tagInput.tags.length; i++) {
+ if (!val || tagInput.tags[i]['name'].substr(0, val.length).toUpperCase() == val.toUpperCase()) { // Check if input value matches each word
+ if (!tagInput.set.has(tagInput.tags[i]['name'])) { // only autocomplete tags not already added
+ // Add autocomplete item container to list
+ let tagListItem = doc.createElement("LI");
+ tagListItem.setAttribute("data-tagName", tagInput.tags[i]['name']);
+ tagListItem.setAttribute("data-tagColor", tagInput.tags[i]['color']);
+ tagListItem.setAttribute("class", "tag-item");
+
+ // Add tag to item container
+ let tagItem = doc.createElement("DIV");
+ tagItem.setAttribute("class", "tag-label-autocomplete");
+ tagItem.setAttribute("style", "background-color: #" + tagInput.tags[i]['color'] + ";");
+ tagItem.innerHTML = tagInput.tags[i]['name'];
+
+ tagListItem.appendChild(tagItem);
+
+ tagListItem.addEventListener("click", function(e) { // Sets input to selected list item
+ let tagName = this.getAttribute("data-tagName"),
+ tagColor = this.getAttribute("data-tagColor");
+ tagInput.elem.value = "";
+ addTagToInput(tagName, tagColor, tagInput);
+ });
+ tagList.appendChild(tagListItem);
+ }
+ }
+ }
+}
+
+// Handles key presses for the tag input
+function handleTagInputKeys(e) {
+ let tagListItems = geTagListItems(this.elem.id, this),
+ tagItems = getTagItems(this.container);
+ if (e.keyCode == 40) { // DOWN arrow key
+ e.preventDefault();
+ this.currentAutocompleteFocus++;
+ addActiveListTag(tagListItems, this);
+ } else if (e.keyCode == 38) { // UP arrow key
+ e.preventDefault();
+ this.currentAutocompleteFocus--;
+ addActiveListTag(tagListItems, this);
+ } else if (e.keyCode == 37) { // LEFT arrow key
+ if (tagItems.length > 0 && this.elem.value.length === 0) {
+ e.preventDefault();
+ this.currentTagFocus--;
+ addActiveTags(tagItems, this);
+ }
+ } else if (e.keyCode == 39) { // RIGHT arrow key
+ if (tagItems.length > 0 && this.elem.value.length === 0) {
+ e.preventDefault();
+ this.currentTagFocus++;
+ addActiveTags(tagItems, this);
+ }
+ } else if(e.keyCode == 8) { // DELETE arrow key
+ if (tagItems.length > 0 && this.currentTagFocus < -1) {
+ e.preventDefault();
+ deleteSelectedTag(tagItems, this);
+ } else if (this.currentTagFocus === -1 && this.elem.value.length < 1) {
+ this.currentTagFocus--;
+ addActiveTags(tagItems, this);
+ }
+ } else if (e.keyCode == 13) { // ENTER key simulates click on list item
+ e.preventDefault();
+ if (tagListItems.length > 0 && this.currentAutocompleteFocus > -1) {
+ if (tagListItems) tagListItems[this.currentAutocompleteFocus].click();
+ } else if (this.elem.value.length > 0
+ && tagListItems.length == 0
+ && this.context !== "moderation") { // check if no autocomplete suggestions -> meaning new tag creation
+ let newTagName = this.elem.value,
+ newTagColor = generateRandomColor();
+ this.elem.value = "";
+ addTagToInput(newTagName, newTagColor, this);
+ }
+ } else if (e.keyCode == 9) { // TAB or SHIFT key simulates click on list item
+ removeActiveTags(tagItems);
+ this.currentTagFocus = -1;
+ if (this.currentAutocompleteFocus > -1) e.preventDefault(); // If no input for adding tag, then allow default tagging to navigate
+ if (tagListItems.length > 0 && this.currentAutocompleteFocus > -1) {
+ if (tagListItems) tagListItems[this.currentAutocompleteFocus].click();
+ } else if (this.elem.value.length > 0 && tagListItems.length == 0) { // check if no autocomplete suggestions -> meaning new tag creation
+ let newTagName = this.elem.value,
+ newTagColor = generateRandomColor();
+ this.elem.value = "";
+ addTagToInput(newTagName, newTagColor, this);
+ }
+ this.currentAutocompleteFocus = -1;
+ closeAllLists(null, this.elem);
+ }
+}
+
+// Create delete tag button on tag labels in tag input
+function createDeleteTagBtn(tagName, tagInput) {
+ // Add delete tag button to tag label
+ let doc = $(document).get(0);
+ let tagInputDeleteBtn = doc.createElement("SPAN");
+ tagInputDeleteBtn.setAttribute("data-tagName",tagName);
+ tagInputDeleteBtn.setAttribute("class","tag-delete-btn ml-1");
+ tagInputDeleteBtn.innerHTML = "x";
+
+ tagInputDeleteBtn.addEventListener("click", function(e) {
+ deleteTag(this.getAttribute("data-tagName"));
+ // Prevents event propagation up DOM tree so parent's (tag container) click event doesn't occur
+ e.stopPropagation();
+ tagInput.set.delete(tagName); // Delete tags from tag input set
+ tagInput.elem.focus();
+ });
+
+ return tagInputDeleteBtn;
+}
+
+// Deletes the tag label associated to delete button
+function deleteTag(tagName) {
+ let doc = $(document).get(0);
+ let tag = doc.getElementById(tagName + "-tag-input");
+ tag.remove()
+}
+
+function addTagToInput(tagName, tagColor, tagInput) {
+ // Add tag label to tag input list
+ let doc = $(document).get(0);
+ let tagInputItem = doc.createElement("LI");
+ tagInputItem.setAttribute("id", tagName + "-tag-input");
+ tagInputItem.setAttribute("data-tagName",tagName);
+ tagInputItem.setAttribute("data-tagColor",tagColor);
+ tagInputItem.setAttribute("class", "tag-label-input")
+ tagInputItem.setAttribute("style", "background-color: #" + tagColor + ";");
+ tagInputItem.setAttribute("tabindex", 0);
+ tagInputItem.innerHTML = tagName;
+ tagInputItem.addEventListener("keydown", handleTagInputKeys.bind(tagInput));
+ // Click event for focusing on tags in input
+ tagInputItem.addEventListener("click", function () {
+ let tagItems = getTagItems(tagInput.container); // [tags] + [inputElement]
+ // ex: tagItems = [tag1,tag2,tag3,input] => target: tag3, index: 2 (-2) => -2 (tagFocus) = 2 (indexOf) - 4 (tagItems.length) as desired
+ tagInput.currentTagFocus = tagItems.indexOf(this) - tagItems.length;
+ addActiveTags(tagItems, tagInput);
+ });
+
+ // Add delete tag to tag label
+ let tagInputDeleteBtn = createDeleteTagBtn(tagName, tagInput);
+ tagInputItem.appendChild(tagInputDeleteBtn);
+
+ tagInput.list.insertBefore(tagInputItem, tagInput.list.children[tagInput.list.children.length-1]);
+ tagInput.set.add(tagName);
+
+ if (tagInput.context !== "moderation") {
+ closeAllLists(null, tagInput);
+ }
+ if (tagInput.context === "moderation") {
+ displayTags(tagInput.elem.value, tagInput);
+ }
+}
+
+function deleteSelectedTag(tagItems, tagInput) {
+ removeActiveTags(tagItems);
+ let doc = $(document).get(0);
+ if (Math.abs(tagInput.currentTagFocus) > tagItems.length) {
+ tagInput.currentTagFocus = -tagItems.length; // handles index beyond first tag elem
+ }
+ // ex: tagItems = [tag1,tag2,tag3,input] => target: tag3, index: 2 (-2) => 4 (tagItems.length) + -2 (tagFocus) = 2 as desired
+ let tag = tagItems[tagItems.length+tagInput.currentTagFocus];
+ let tagName = tag.dataset.tagname;
+ let tagColor = tag.dataset.tagcolor;
+ tag.remove();
+ tagInput.set.delete(tag.getAttribute("data-tagName"));
+ tagItems.splice(tagItems.length+tagInput.currentTagFocus, 1); // recountruct tag input items after removal
+ if (tagItems.length === 1) {
+ tagInput.currentTagFocus = -1; // if only one item left, must be tag input elem which needs tagFocus = -1
+ }
+ addActiveTags(tagItems, tagInput); // focus next item in the list
+ if (tagInput.context === "moderation") {
+ displayTags("", tagInput);
+ }
+}
+
+// Makes item active in autocomplete dropdown
+function addActiveListTag(items, tagInput) {
+ if (items.length === 0 || (tagInput.currentAutocompleteFocus == -1 && items.length === 0)) return false;
+ removeActiveListTag(items);
+ if (tagInput.currentAutocompleteFocus >= items.length) tagInput.currentAutocompleteFocus = 0;
+ if (tagInput.currentAutocompleteFocus < 0) tagInput.currentAutocompleteFocus = (items.length - 1);
+ items[tagInput.currentAutocompleteFocus].setAttribute("id","autocomplete-active");
+}
+
+// Remove active class from autocomplete items
+function removeActiveListTag(items) {
+ for (var i = 0; i < items.length; i++) {
+ items[i].removeAttribute("id");
+ }
+}
+
+// Makes item active in added tagged
+function addActiveTags(items, tagInput) {
+ removeActiveTags(items);
+ if (tagInput.currentTagFocus >= -1) { // focus input from tags to (typing) input
+ tagInput.currentTagFocus = -1;
+ tagInput.elem.focus();
+ } else { // blur input (typing) in tag input if on tag and not entering input
+ tagInput.elem.blur();
+ if (tagInput.context !== "moderation") {
+ closeAllLists(null, tagInput);
+ }
+ }
+ if (items.length === 0 || tagInput.currentTagFocus === -1) return false;
+
+ if (Math.abs(tagInput.currentTagFocus) >= items.length) tagInput.currentTagFocus = -items.length;
+
+ items[items.length+tagInput.currentTagFocus].focus();
+ items[items.length+tagInput.currentTagFocus].classList.add("tag-focus");
+}
+
+// Gets the current autcomplete items
+function geTagListItems(inputId, tagInput) {
+ let doc = $(document).get(0);
+ let items;
+ if (tagInput.context !== "moderation") {
+ items = doc.getElementById(inputId + "-autocomplete-list");
+ } else {
+ items = doc.getElementById(inputId + "-mod-list");
+ }
+ if (items) items = items.getElementsByTagName("LI");
+ else items = []
+ return items
+}
+
+// Gets the current tag items
+function getTagItems(tagInputContainer) {
+ let doc = $(document).get(0);
+ let items = doc.getElementById("tag-input-list");
+ if (items) {
+ items = [...items.getElementsByClassName("tag-label-input")];
+ if (items.length > 0) items.push(tagInputContainer);
+ } else items = [];
+ return items
+}
+
+// Remove active class from tag items
+function removeActiveTags(items) {
+ for (var i = 0; i < items.length; i++) {
+ items[i].blur();
+ items[i].classList.remove("tag-focus");
+ }
+}
+
+// Closes all lists except for selected list
+function closeAllLists(element=null, tagInput) {
+ let itemsToClose;
+ if (tagInput.context !== "moderation") {
+ itemsToClose = $(".tag-items");
+ } else {
+ itemsToClose = $(".tag-items-mod");
+ }
+ for (let i = 0; i < itemsToClose.length; i++) {
+ if (element != itemsToClose[i] && element != tagInput.elem) {
+ itemsToClose[i].parentNode.removeChild(itemsToClose[i]);
+ }
+ }
+}
+
+// Generates random colors that valid and are not pure white
+function generateRandomColor() {
+ let randomColor = Math.floor(Math.random()*16777215).toString(16);
+ if(randomColor.length != 6 || randomColor == "ffffff"){ // In any case, the color code is invalid or pure white
+ randomColor = generateRandomColor();
+ }
+ return randomColor;
+}
+
+export { autocomplete, getTagItems, removeActiveTags, closeAllLists, displayTags, addTagToInput, handleTagInputKeys };
\ No newline at end of file
diff --git a/http_handler/static/javascript/murmur/modules/tag_subscription.js b/http_handler/static/javascript/murmur/modules/tag_subscription.js
new file mode 100644
index 00000000..1d11264e
--- /dev/null
+++ b/http_handler/static/javascript/murmur/modules/tag_subscription.js
@@ -0,0 +1,51 @@
+// Click listener for the whole row to be clickable and toggle blocking/subscribing
+function addRowSelect(selectRows, selectedTagsSet) {
+ selectRows.each((index, elem) => {
+ elem.addEventListener("click", (e) => {
+ if (!selectedTagsSet.has(e.target)) elem.firstElementChild.firstElementChild.click()
+ });
+ });
+}
+
+// Select column with block/checkmark icons to select rows
+function handleTagChanges(selectedTags, tags, mutedTags, followedTags, swapping, updateEmails) {
+ selectedTags.each((index, elem) => {
+ elem.addEventListener("click", function() {
+ const mode = $('input[name="tag-mode"]:checked').val();
+ const tag = elem.parentNode.nextElementSibling.firstElementChild;
+ elem.toggleAttribute("checked");
+ elem.classList.toggle("inactive");
+
+ if (!swapping) {
+ const isSelected = elem.hasAttribute("checked")
+ if (mode == "block-mode") {
+ if (isSelected) mutedTags.add(tag.innerHTML);
+ else mutedTags.delete(tag.innerHTML);
+ } else if (mode == "subscribe-mode") {
+ if (isSelected) mutedTags.delete(tag.innerHTML);
+ else mutedTags.add(tag.innerHTML);
+ }
+ }
+ swapping = false;
+ if (updateEmails !== undefined) updateEmails(tags, mutedTags, followedTags);
+ })
+ });
+}
+
+// Toggles visibility of tags based on tag mode change
+function handleModeChanges(modeInput, selectedTags, swapping) {
+ modeInput.change(function() {
+ const mode = $('input[name="tag-mode"]:checked').val();
+ selectedTags.each((index, elem) => {
+ if (mode == "block-mode") {
+ elem.setAttribute("src", "/static/css/third-party/images/block.svg");
+ } else if (mode == "subscribe-mode") {
+ elem.setAttribute("src", "/static/css/third-party/images/check.svg");
+ }
+ swapping = true;
+ elem.click()
+ });
+ });
+}
+
+export { addRowSelect, handleTagChanges, handleModeChanges };
\ No newline at end of file
diff --git a/http_handler/static/javascript/murmur/my_group_settings.js b/http_handler/static/javascript/murmur/my_group_settings.js
index d3b8cf00..c31877f3 100644
--- a/http_handler/static/javascript/murmur/my_group_settings.js
+++ b/http_handler/static/javascript/murmur/my_group_settings.js
@@ -1,21 +1,22 @@
+import { addRowSelect, handleTagChanges, handleModeChanges } from './modules/tag_subscription.js';
+
$(document).ready(function(){
-
- let user_name = $.trim($('#user_email').text());
- group_name = $.trim($("#group-name").text());
+ let user_name = $.trim($('#user_email').text()),
+ group_name = $.trim($("#group-name").text()),
btn_add_dissimulate = $("#btn-add-dissimulate"),
- btn_delete_dissimulate = $("#btn-delete-dissimulate");
- btn_save_settings = $("#btn-save-settings");
- btn_cancel_settings = $("#btn-cancel-settings");
- selectRows = $(".my_row");
- modeInput = $('input[name="tag-mode"]');
- selectTags = $('img[data-type="tag-select"]'); // select elements (block/check icons)
- tags = $(".tag").toArray().map(e => e.innerHTML);
- selectTagsSet = new Set(selectTags.toArray());
- followedTags = new Set(tag_subscription["followed"]);
- mutedTags = new Set(tag_subscription["muted"]);
+ btn_delete_dissimulate = $("#btn-delete-dissimulate"),
+ btn_save_settings = $("#btn-save-settings"),
+ btn_cancel_settings = $("#btn-cancel-settings"),
+ selectRows = $(".my_row"),
+ modeInput = $('input[name="tag-mode"]'),
+ selectedTags = $('img[data-type="tag-select"]'), // select elements (block/check icons)
+ tags = $(".tag").toArray().map(e => e.innerHTML),
+ selectedTagsSet = new Set(selectedTags.toArray()),
+ followedTags = new Set(tag_subscription["followed"]),
+ mutedTags = new Set(tag_subscription["muted"]),
swapping = false;
- getSavedTagSubscription();
+ getSavedTagSubscription(selectedTags, mutedTags, followedTags);
// Gray out notifications and tag subscription section to indicate no emails will be sent
if ($('#no-emails').is(":checked")) {
@@ -55,63 +56,15 @@ $(document).ready(function(){
},
});
- let tag_demo_table = $("#tag-demo-table").DataTable({
- "columns": [ { 'orderable': false}, null, null, null],
- "order": [[1, "asc"]],
- responsive: {
- details: {
- type: 'column',
- target: 'tr'
- }
- },
- searching: false,
- paginate: false,
- info: false,
- });
-
// Click listener for the whole row to be clickable and toggle blocking/subscribing
- selectRows.each((index, elem) => {
- elem.addEventListener("click", (e) => {
- if (!selectTagsSet.has(e.target)) elem.firstElementChild.firstElementChild.click()
- });
- });
+ addRowSelect(selectRows, selectedTagsSet);
- // Select column with block and checkmark icons to select rows
- selectTags.each((index, elem) => {
- elem.addEventListener("click", function() {
- const mode = $('input[name="tag-mode"]:checked').val();
- const tag = elem.parentNode.nextElementSibling.firstElementChild;
- elem.toggleAttribute("checked");
- elem.classList.toggle("inactive");
+ // Select column with block/checkmark icons to select rows
+ handleTagChanges(selectedTags, tags, mutedTags, followedTags, swapping);
- if (!swapping) {
- const isSelected = elem.hasAttribute("checked")
- if (mode == "block-mode") {
- if (isSelected) mutedTags.add(tag.innerHTML);
- else mutedTags.delete(tag.innerHTML);
- } else if (mode == "subscribe-mode") {
- if (isSelected) mutedTags.delete(tag.innerHTML);
- else mutedTags.add(tag.innerHTML);
- }
- }
- swapping = false;
- })
- });
-
// Toggles visibility of tags based on tag mode change
- modeInput.change(function() {
- const mode = $('input[name="tag-mode"]:checked').val();
- selectTags.each((index, elem) => {
- if (mode == "block-mode") {
- elem.setAttribute("src", "/static/css/third-party/images/block.svg");
- } else if (mode == "subscribe-mode") {
- elem.setAttribute("src", "/static/css/third-party/images/check.svg");
- }
- swapping = true;
- elem.click()
- });
- });
-
+ handleModeChanges(modeInput, selectedTags, swapping);
+
toggle_edit_emails();
$('#ck-no-email').change(function() {
@@ -119,7 +72,7 @@ $(document).ready(function(){
});
- edit_group_settings =
+ let edit_group_settings =
function(params){
const mode = $('input[name="tag-mode"]:checked').val();
params.all_emails = $('#all-emails').is(":checked");
@@ -143,7 +96,7 @@ $(document).ready(function(){
bind_buttons();
function toggle_edit_emails() {
- no_emails = $('#ck-no-email').is(":checked");
+ let no_emails = $('#ck-no-email').is(":checked");
if (no_emails) {
$('#edit-emails').css({"color": "gray"});
$('#rdo-follow').attr('disabled', true);
@@ -162,9 +115,9 @@ $(document).ready(function(){
}
function bind_buttons() {
- var params = {'group_name': group_name};
+ let params = {'group_name': group_name};
- var save_settings = bind(edit_group_settings, params);
+ let save_settings = bind(edit_group_settings, params);
btn_save_settings.unbind("click");
btn_cancel_settings.unbind("click");
@@ -249,10 +202,10 @@ function notify(res, on_success){
}
// Updates UI based on saved user tag subscription settings
-function getSavedTagSubscription() {
+function getSavedTagSubscription(selectedTags, mutedTags, followedTags) {
const mode = $('input[name="tag-mode"]:checked').val();
if (mode == "block-mode") {
- selectTags.each((index,elem) => {
+ selectedTags.each((index,elem) => {
const tag = elem.parentNode.nextElementSibling.firstElementChild;
if (mutedTags.has(tag.innerHTML)) {
elem.toggleAttribute("checked");
@@ -260,7 +213,7 @@ function getSavedTagSubscription() {
}
})
} else if (mode == "subscribe-mode") {
- selectTags.each((index,elem) => {
+ selectedTags.each((index,elem) => {
const tag = elem.parentNode.nextElementSibling.firstElementChild;
if (followedTags.has(tag.innerHTML)) {
elem.toggleAttribute("checked");
diff --git a/http_handler/static/javascript/murmur/thread_page.js b/http_handler/static/javascript/murmur/thread_page.js
index 270beedd..ce1fe3e8 100644
--- a/http_handler/static/javascript/murmur/thread_page.js
+++ b/http_handler/static/javascript/murmur/thread_page.js
@@ -1,3 +1,5 @@
+import { autocomplete, getTagItems, removeActiveTags, displayTags, addTagToInput, handleTagInputKeys } from './modules/tag_input.js';
+
$(document).ready(function(){
if ($("#highlight_post").length){
@@ -7,7 +9,7 @@ $(document).ready(function(){
if ($('#reply-text-input').length) {
CKEDITOR.replace( 'reply-text-input' );
}
-
+
var gmail_quotes = $(".gmail_quote");
var check = "---------- Forwarded message ----------";
@@ -18,6 +20,36 @@ $(document).ready(function(){
$(this).wrap( "
" );
}
});
+
+ const tagInput = {
+ context: "moderation",
+ container: $(".tag-input-container").get(0),
+ elem: $("#tag-input").get(0),
+ tags: django_tag_data["tags"],
+ list: $("#tag-input-list").get(0),
+ set: new Set(), // used to more efficiently determine if a certain tag exists
+ currentAutocompleteFocus: -1,
+ currentTagFocus: -1 // negative index of tag input item currently focused ex: [tag1,tag2,tag3,{tagInput}], {} = focused
+ }
+ const threadTags = django_thread_tags["tags"];
+ // Initially add original poster's tags for moderation into input
+ if (threadTags.length > 0) {
+ displayTags("", tagInput);
+ for (const tag of threadTags) {
+ addTagToInput(tag.name, tag.color, tagInput);
+ }
+ } else {
+ }
+
+ /* Listener for tag input box to moderate tags once it clicked and focused on */
+ tagInput.elem.addEventListener("focus", (event) => {
+ tagInput.currentTagFocus = -1;
+ removeActiveTags(getTagItems(tagInput.container));
+ autocomplete(tagInput);
+ });
+
+ /* Listener for tag input box to handle key press actions (e.g. navigating moderation tags, navigating added tags) */
+ tagInput.elem.addEventListener("keydown", handleTagInputKeys.bind(tagInput));
// var block = $( ".moz-cite-prefix" ).next();
diff --git a/static/images/favicon/favicon-192x192.svg b/static/images/favicon/favicon-192x192.svg
new file mode 100644
index 00000000..e69de29b