Commit 108358bf by Jonathan Piacenti

Polished up backend. Markdown needs extraction. Frontend needs further CSS.

parent f56c62d4
"""TO-DO: Write a description of what this XBlock is."""
from collections import OrderedDict
from django.template import Template, Context
from bleach import clean
from markdown import markdown
import pkg_resources
from xblock.core import XBlock
from xblock.fields import Scope, List, Integer, String, Dict
from xblock.fields import Scope, List, String, Dict, UserScope, BlockScope
from xblock.fragment import Fragment
......@@ -21,12 +24,8 @@ class PollBlock(XBlock):
scope=Scope.settings, help="The questions on this poll."
)
tally = Dict(default={'Red': 0, 'Blue': 0, 'Green': 0, 'Other': 0},
scope=Scope.user_state_summary,
scope=Scope.content,
help="Total tally of answers from students.")
# No default. Hopefully this will yield 'None', or do something
# distinctive when queried.
# Choices are always one above their place in the index so that choice
# is never false if it's provided.
choice = String(scope=Scope.user_state, help="The student's answer")
def resource_string(self, path):
......@@ -36,7 +35,7 @@ class PollBlock(XBlock):
@XBlock.json_handler
def get_results(self, data, suffix=''):
return {'question': self.question, 'tally': self.tally_detail()}
return {'question': markdown(clean(self.question)), 'tally': self.tally_detail()}
def tally_detail(self):
"""
......@@ -44,13 +43,15 @@ class PollBlock(XBlock):
"""
tally = []
answers = OrderedDict(self.answers)
choice = self.get_choice()
total = 0
for key, value in answers.items():
tally.append({
'count': int(self.tally.get(key, 0)),
'answer': value,
'key': key,
'top': False
'top': False,
'choice': False
})
total += tally[-1]['count']
......@@ -58,6 +59,8 @@ class PollBlock(XBlock):
try:
percent = (answer['count'] / float(total))
answer['percent'] = int(percent * 100)
if answer['key'] == choice:
answer['choice'] = True
except ZeroDivisionError:
answer['percent'] = 0
......@@ -69,6 +72,17 @@ class PollBlock(XBlock):
tally[0]['top'] = True
return tally
def get_choice(self):
"""
It's possible for the choice to have been removed since
the student answered the poll. We don't want to take away
the user's progress, but they should be able to vote again.
"""
if self.choice and self.choice in OrderedDict(self.answers):
return self.choice
else:
return None
# TO-DO: change this view to display your data your own way.
def student_view(self, context=None):
"""
......@@ -81,11 +95,13 @@ class PollBlock(XBlock):
js_template = self.resource_string(
'/public/handlebars/results.handlebars')
choice = self.get_choice()
context.update({
'choice': self.choice,
'choice': choice,
# Offset so choices will always be True.
'answers': self.answers,
'question': self.question,
'question': markdown(clean(self.question)),
'js_template': js_template,
})
......@@ -116,7 +132,7 @@ class PollBlock(XBlock):
js_template = self.resource_string('/public/handlebars/studio.handlebars')
context.update({
'question': self.question,
'question': clean(self.question),
'js_template': js_template
})
context = Context(context)
......@@ -126,6 +142,15 @@ class PollBlock(XBlock):
frag.add_javascript_url(
self.runtime.local_resource_url(
self, 'public/js/vendor/handlebars.js'))
frag.add_javascript_url(
self.runtime.local_resource_url(
self, 'public/js/vendor/pen.js'))
frag.add_javascript_url(
self.runtime.local_resource_url(
self, 'public/js/vendor/markdown.js'))
frag.add_css_url(
self.runtime.local_resource_url(
self, 'public/css/vendor/pen.css'))
frag.add_css(self.resource_string('/public/css/poll_edit.css'))
frag.add_javascript(self.resource_string("public/js/poll_edit.js"))
frag.initialize_js('PollEditBlock')
......@@ -135,31 +160,49 @@ class PollBlock(XBlock):
@XBlock.json_handler
def studio_submit(self, data, suffix=''):
# I wonder if there's something for live validation feedback already.
result = {'success': True, 'errors': {'fields': {}, 'general': []}}
result = {'success': True, 'errors': []}
if 'question' not in data or not data['question']:
result['errors']['question'] = "This field is required."
result['errors'].append("You must specify a question.")
result['success'] = False
# Need this meta information, otherwise the questions will be
# shuffled by Python's dictionary data type.
poll_order = [key.strip().replace('answer-', '')
for key in data.get('poll_order', [])
]
# Aggressively clean/sanity check answers list.
answers = OrderedDict(
(key.replace('answer-', '', 1), value.strip()[:250])
for key, value in data.items()
if (key.startswith('answer-') and not key == 'answer-')
and not value.isspace()
)
answers = []
for key, value in data.items():
if not key.startswith('answer-'):
continue
key = key.replace('answer-', '')
if not key or key.isspace():
continue
value = value.strip()[:250]
if not value or value.isspace():
continue
if key in poll_order:
answers.append((key, value))
if not len(answers) > 1:
result['errors']['general'].append(
result['errors'].append(
"You must include at least two answers.")
result['success'] = False
if not result['success']:
return result
self.answers = answers.items()
# Need to sort the answers.
answers.sort(key=lambda x: poll_order.index(x[0]), reverse=True)
self.answers = answers
self.question = data['question']
print answers
tally = self.tally
answers = OrderedDict(answers)
# Update tracking schema.
for key, value in answers.items():
if key not in tally:
......@@ -176,24 +219,28 @@ class PollBlock(XBlock):
"""
An example handler, which increments the data.
"""
result = {'success': False}
if self.choice is not None:
result['message'] = 'You cannot vote twice.'
result = {'success': False, 'errors': []}
if self.get_choice() is not None:
result['errors'].append('You have already voted in this poll.')
return result
try:
choice = data['choice']
except KeyError:
result['message'] = 'Answer not included with request.'
result['errors'].append('Answer not included with request.')
return result
# Just to show data coming in...
try:
OrderedDict(self.answers)[choice]
except KeyError:
result['message'] = 'No key "{choice}" in answers table.'.format(choice=choice)
result['errors'].append('No key "{choice}" in answers table.'.format(choice=choice))
return result
self.choice = choice
self.tally[choice] += 1
tally = self.tally.copy()
tally[choice] = self.tally.get(choice, 0)
self.tally = tally
# Let the LMS know the user has answered the poll.
self.runtime.publish(self, 'progress', {})
result['success'] = True
return result
......
.poll-answer {
margin-left: 1em;
font-weight: bold;
}
\ No newline at end of file
......@@ -3,3 +3,24 @@
.poll-delete-answer {
float: right;
}
#question-editor-container{
width: 100%;
text-align: center;
}
#question-editor{
width: 98%;
height: 7em;
text-align: left;
color: #4C4C4C;
margin-top: 0.5em;
margin-bottom: 0.5em;
box-shadow: 0px 0px 9px #555 inset;
border: 1px solid #B2B2B2;
border-radius: 3px;
padding: 10px;
}
h2 label {
font-weight: bold;
font-size: 16pt;
}
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2012 by original authors @ fontello.com</metadata>
<defs>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="location" unicode="&#xe815;" d="M429 493q0 59-42 101t-101 42-101-42-42-101 42-101 101-42 101 42 42 101z m143 0q0-61-18-100l-203-432q-9-18-27-29t-38-11-38 11-26 29l-204 432q-18 39-18 100 0 118 84 202t202 84 202-84 84-202z" horiz-adv-x="571.429" />
<glyph glyph-name="fit" unicode="&#xe80f;" d="M429 314l0-250q0-15-11-25t-25-11-25 11l-80 80-185-185q-6-6-13-6t-13 6l-64 64q-6 6-6 13t6 13l185 185-80 80q-11 11-11 25t11 25 25 11l250 0q15 0 25-11t11-25z m421 375q0-7-6-13l-185-185 80-80q11-11 11-25t-11-25-25-11l-250 0q-15 0-25 11t-11 25l0 250q0 15 11 25t25 11 25-11l80-80 185 185q6 6 13 6t13-6l64-64q6-6 6-13z" horiz-adv-x="857.143" />
<glyph glyph-name="bold" unicode="&#xe805;" d="M310 1q42-18 78-18 73 0 121 23t68 63q21 39 21 101 0 64-23 100-32 52-79 70-45 18-138 18-41 0-56-6l0-80-1-97 2-151q0-8 7-25z m-8 416q24-4 61-4 98 0 147 36t50 125q0 62-47 104t-142 42q-29 0-73-7 0-25 1-43 4-68 3-156l-1-55q0-24 1-43z m-302-496l1 52q25 5 38 7 43 7 69 17 9 15 12 28 5 37 5 108l-1 277q-3 143-5 225-1 49-6 61-1 2-7 7-10 7-39 8-17 1-64 7l-2 46 145 3 212 7 25 1q3 0 8 0t8 0q1 0 12 0t23 0l41 0q49 0 107-15 24-7 54-22 32-16 57-42t36-58 12-68q0-39-18-71t-53-59q-15-11-84-43 99-23 149-81 51-59 51-132 0-42-16-90-12-35-40-65-37-40-78-60t-113-33q-46-8-110-6l-110 2q-47 1-166-6-18-2-152-6z" horiz-adv-x="785.714" />
<glyph glyph-name="italic" unicode="&#xe806;" d="M0-77l9 47q2 1 43 11 42 11 65 22 16 21 23 56l15 78 31 150 7 36q4 25 9 47t9 37 7 26 5 17 2 6l16 88 9 35 12 75 4 28 0 21q-23 12-80 16-16 1-21 2l11 57 177-8q22-1 41-1 37 0 119 5 18 1 38 3t20 1q-1-11-3-21-4-16-7-28-31-11-61-17-36-9-56-17-7-17-13-49-5-25-7-46-25-111-37-171l-34-174-21-88-24-131-7-25q-1-4 1-15 36-8 66-12 20-3 37-6-1-16-4-32-4-17-5-23-10 0-13-1-13-1-23-1-5 0-16 2t-81 9l-110 1q-23 1-97-6-41-4-55-5z" horiz-adv-x="571.429" />
<glyph glyph-name="justifyleft" unicode="&#xe80a;" d="M1000 100l0-71q0-15-11-25t-25-11l-929 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l929 0q15 0 25-11t11-25z m-214 214l0-71q0-15-11-25t-25-11l-714 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l714 0q15 0 25-11t11-25z m143 214l0-71q0-15-11-25t-25-11l-857 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l857 0q15 0 25-11t11-25z m-214 214l0-71q0-15-11-25t-25-11l-643 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l643 0q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="justifycenter" unicode="&#xe80b;" d="M1000 100l0-71q0-15-11-25t-25-11l-929 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l929 0q15 0 25-11t11-25z m-214 214l0-71q0-15-11-25t-25-11l-500 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l500 0q15 0 25-11t11-25z m143 214l0-71q0-15-11-25t-25-11l-786 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l786 0q15 0 25-11t11-25z m-214 214l0-71q0-15-11-25t-25-11l-357 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l357 0q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="justifyright" unicode="&#xe80c;" d="M1000 100l0-71q0-15-11-25t-25-11l-929 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l929 0q15 0 25-11t11-25z m0 214l0-71q0-15-11-25t-25-11l-714 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l714 0q15 0 25-11t11-25z m0 214l0-71q0-15-11-25t-25-11l-857 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l857 0q15 0 25-11t11-25z m0 214l0-71q0-15-11-25t-25-11l-643 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l643 0q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="justifyfull" unicode="&#xe80d;" d="M1000 100l0-71q0-15-11-25t-25-11l-929 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l929 0q15 0 25-11t11-25z m0 214l0-71q0-15-11-25t-25-11l-929 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l929 0q15 0 25-11t11-25z m0 214l0-71q0-15-11-25t-25-11l-929 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l929 0q15 0 25-11t11-25z m0 214l0-71q0-15-11-25t-25-11l-929 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l929 0q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="outdent" unicode="&#xe800;" d="M214 546l0-321q0-7-5-13t-13-5q-8 0-13 5l-161 161q-5 5-5 13t5 13l161 161q5 5 13 5 7 0 13-5t5-13z m786-429l0-107q0-7-5-13t-13-5l-964 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l964 0q7 0 13-5t5-13z m0 214l0-107q0-7-5-13t-13-5l-607 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l607 0q7 0 13-5t5-13z m0 214l0-107q0-7-5-13t-13-5l-607 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l607 0q7 0 13-5t5-13z m0 214l0-107q0-7-5-13t-13-5l-964 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l964 0q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="indent" unicode="&#xe801;" d="M196 386q0-8-5-13l-161-161q-5-5-13-5-7 0-13 5t-5 13l0 321q0 7 5 13t13 5q8 0 13-5l161-161q5-5 5-13z m804-268l0-107q0-7-5-13t-13-5l-964 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l964 0q7 0 13-5t5-13z m0 214l0-107q0-7-5-13t-13-5l-607 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l607 0q7 0 13-5t5-13z m0 214l0-107q0-7-5-13t-13-5l-607 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l607 0q7 0 13-5t5-13z m0 214l0-107q0-7-5-13t-13-5l-964 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l964 0q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="mode" unicode="&#xe813;" d="M429 46l0 607q-83 0-152-41t-110-110-41-152 41-152 110-110 152-41z m429 304q0-117-57-215t-156-156-215-57-215 57-156 156-57 215 57 215 156 156 215 57 215-57 156-156 57-215z" horiz-adv-x="857.143" />
<glyph glyph-name="fullscreen" unicode="&#xe80e;" d="M716 548l-198-198 198-198 80 80q16 17 39 8 22-9 22-33l0-250q0-15-11-25t-25-11l-250 0q-23 0-33 22-9 22 8 39l80 80-198 198-198-198 80-80q17-17 8-39t-33-22l-250 0q-15 0-25 11t-11 25l0 250q0 23 22 33 22 9 39-8l80-80 198 198-198 198-80-80q-11-11-25-11-7 0-13 3-22 9-22 33l0 250q0 15 11 25t25 11l250 0q23 0 33-22 9-22-8-39l-80-80 198-198 198 198-80 80q-17 17-8 39t33 22l250 0q15 0 25-11t11-25l0-250q0-23-22-33-7-3-14-3-15 0-25 11z" horiz-adv-x="857.143" />
<glyph glyph-name="insertunorderedlist" unicode="&#xe802;" d="M214 64q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m0 286q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232l0-107q0-7-5-13t-13-5l-679 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l679 0q7 0 13-5t5-13z m-786 518q0-45-31-76t-76-31-76 31-31 76 31 76 76 31 76-31 31-76z m786-232l0-107q0-7-5-13t-13-5l-679 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l679 0q7 0 13-5t5-13z m0 286l0-107q0-7-5-13t-13-5l-679 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l679 0q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="insertorderedlist" unicode="&#xe803;" d="M213-54q0-45-30-70t-76-26q-59 0-96 37l32 49q27-25 59-25 16 0 28 8t12 24q0 36-59 31l-15 31q4 6 18 24t24 30 21 21l0 1q-9 0-27-1t-27-1l0-30-59 0 0 85 186 0 0-49-53-64q28-7 45-27t17-49z m1 350l0-89-202 0q-3 20-3 30 0 28 13 52t32 38 37 27 32 24 13 25q0 14-8 21t-22 8q-26 0-45-32l-47 33q13 28 40 44t59 16q41 0 69-23t28-63q0-28-19-51t-42-36-42-28-20-29l71 0 0 33 59 0z m786-178l0-107q0-7-5-13t-13-5l-679 0q-7 0-13 5t-5 13l0 107q0 8 5 13t13 5l679 0q7 0 13-5t5-13z m-786 502l0-55-187 0 0 55 60 0q0 23 0 68t0 68l0 7-1 0q-4-9-28-30l-40 42 76 71 59 0 0-225 60 0z m786-216l0-107q0-7-5-13t-13-5l-679 0q-7 0-13 5t-5 13l0 107q0 8 5 13t13 5l679 0q7 0 13-5t5-13z m0 286l0-107q0-7-5-13t-13-5l-679 0q-7 0-13 5t-5 13l0 107q0 7 5 13t13 5l679 0q7 0 13-5t5-13z" horiz-adv-x="1000" />
<glyph glyph-name="strikethrough" unicode="&#xe807;" d="M982 350q8 0 13-5t5-13l0-36q0-8-5-13t-13-5l-964 0q-8 0-13 5t-5 13l0 36q0 8 5 13t13 5l964 0z m-713 36q-16 20-28 45-27 54-27 105 0 101 75 172 74 71 219 71 28 0 93-11 37-7 99-27 6-21 12-66 8-69 8-102 0-10-3-25l-7-2-47 3-8 1q-28 83-57 114-49 51-117 51-64 0-102-33-37-32-37-81 0-41 37-78t156-72q39-11 97-37 32-16 53-29l-415 0z m283-143l229 0q4-22 4-51 0-62-23-118-13-31-40-58-21-20-61-45-45-27-85-37-45-12-113-12-64 0-109 13l-78 22q-32 9-40 16-4 4-4 12l0 7q0 60-1 87-1 17 0 38l1 21 0 25 57 1q8-19 17-40t13-31 7-15q20-32 45-52 24-20 59-32 33-12 74-12 36 0 78 15 43 15 68 48 26 34 26 72 0 47-45 88-19 16-76 40z" horiz-adv-x="1000" />
<glyph glyph-name="underline" unicode="&#xe804;" d="M27 726q-21 1-25 2l-2 49q7 1 22 1 33 0 62-2 74-4 93-4 48 0 94 2 65 2 81 3 31 0 48 1l-1-8 1-36 0-5q-33-5-69-5-33 0-44-14-7-8-7-74 0-7 0-18t0-14l1-128 8-156q3-69 28-113 20-33 54-51 49-26 99-26 58 0 107 16 31 10 55 28 27 20 36 36 20 31 30 64 12 41 12 128 0 44-2 71t-6 68-8 89l-2 33q-3 37-13 49-19 20-43 19l-56-1-8 2 1 48 47 0 114-6q42-2 109 6l10-1q3-21 3-28 0-4-2-17-25-7-47-7-41-6-44-9-8-8-8-23 0-4 1-15t1-17q4-11 12-221 3-109-8-170-8-42-23-68-21-36-62-69-42-32-102-50-61-18-142-18-93 0-158 26-66 26-100 68t-46 109q-9 45-9 132l0 186q0 105-9 119-14 20-82 22z m830-786l0 36q0 8-5 13t-13 5l-821 0q-8 0-13-5t-5-13l0-36q0-8 5-13t13-5l821 0q8 0 13 5t5 13z" horiz-adv-x="857.143" />
<glyph glyph-name="blockquote" unicode="&#xe814;" d="M429 671l0-393q0-58-23-111t-61-91-91-61-111-23l-36 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l36 0q59 0 101 42t42 101l0 18q0 22-16 38t-38 16l-125 0q-45 0-76 31t-31 76l0 214q0 45 31 76t76 31l214 0q45 0 76-31t31-76z m500 0l0-393q0-58-23-111t-61-91-91-61-111-23l-36 0q-15 0-25 11t-11 25l0 71q0 15 11 25t25 11l36 0q59 0 101 42t42 101l0 18q0 22-16 38t-38 16l-125 0q-45 0-76 31t-31 76l0 214q0 45 31 76t76 31l214 0q45 0 76-31t31-76z" horiz-adv-x="928.571" />
<glyph glyph-name="undo" unicode="&#xe817;" d="M1000 225q0-93-71-252-2-4-6-13t-8-17-7-12q-7-9-16-9-8 0-13 6t-5 14q0 5 1 15t1 13q3 38 3 69 0 56-10 101t-27 77-45 56-59 39-74 24-86 12-98 3l-125 0 0-143q0-15-11-25t-25-11-25 11l-286 286q-11 11-11 25t11 25l286 286q11 11 25 11t25-11 11-25l0-143 125 0q398 0 488-225 30-75 30-186z" horiz-adv-x="1000" />
<glyph glyph-name="code" unicode="&#xe816;" d="M344 69l-28-28q-6-6-13-6t-13 6l-260 260q-6 6-6 13t6 13l260 260q6 6 13 6t13-6l28-28q6-6 6-13t-6-13l-219-219 219-219q6-6 6-13t-6-13z m330 595l-208-720q-2-7-9-11t-13-1l-35 9q-7 2-11 9t-1 14l208 720q2 7 9 11t13 1l35-9q7-2 11-9t1-14z m367-363l-260-260q-6-6-13-6t-13 6l-28 28q-6 6-6 13t6 13l219 219-219 219q-6 6-6 13t6 13l28 28q6 6 13 6t13-6l260-260q6-6 6-13t-6-13z" horiz-adv-x="1071.429" />
<glyph glyph-name="unlink" unicode="&#xe811;" d="M245 141l-143-143q-6-5-13-5t-13 5q-5 6-5 13t5 13l143 143q6 5 13 5t13-5q5-6 5-13t-5-13z m94-23l0-179q0-8-5-13t-13-5-13 5-5 13l0 179q0 8 5 13t13 5 13-5 5-13z m-125 125q0-8-5-13t-13-5l-179 0q-8 0-13 5t-5 13 5 13 13 5l179 0q8 0 13-5t5-13z m705-71q0-67-47-113l-82-81q-46-46-113-46-68 0-114 47l-186 187q-12 12-23 31l133 10 152-153q15-15 38-15t38 15l82 81q16 16 16 37 0 22-16 38l-153 153 10 133q20-12 31-23l187-187q47-48 47-114z m-344 404l-133-10-152 153q-16 16-38 16t-38-15l-82-81q-16-16-16-37 0-22 16-38l153-153-10-134q-20 12-31 23l-187 187q-47 48-47 114 0 67 47 113l82 81q46 46 113 46 68 0 114-47l186-187q12-12 23-31z m353-47q0-8-5-13t-13-5l-179 0q-8 0-13 5t-5 13 5 13 13 5l179 0q8 0 13-5t5-13z m-304 304l0-179q0-8-5-13t-13-5-13 5-5 13l0 179q0 8 5 13t13 5 13-5 5-13z m227-84l-143-143q-6-5-13-5t-13 5q-5 6-5 13t5 13l143 143q6 5 13 5t13-5q5-6 5-13t-5-13z" horiz-adv-x="928.571" />
<glyph glyph-name="superscript" unicode="&#xe808;" d="M501 86l0-93-138 0-89 141-13 23q-4 5-6 12l-2 0-5-12q-6-11-14-25l-86-140-144 0 0 93 71 0 110 162-103 152-76 0 0 94 154 0 78-127q1-2 13-23 4-5 6-12l2 0q2 5 6 12l14 23 78 127 143 0 0-94-70 0-103-149 114-165 61 0z m355 379l0-115-287 0-2 15q-2 16-2 26 0 36 15 65t36 48 47 36 47 30 36 30 15 36q0 21-16 35t-39 14q-28 0-54-22-8-6-20-21l-59 51q15 21 35 37 46 36 105 36 61 0 99-33t38-88q0-31-14-57t-35-43-45-33-46-28-37-29-17-35l129 0 0 45 70 0z" horiz-adv-x="857.143" />
<glyph glyph-name="subscript" unicode="&#xe809;" d="M501 86l0-93-138 0-89 141-13 23q-4 5-6 12l-2 0-5-12q-6-11-14-25l-86-140-144 0 0 93 71 0 110 162-103 152-76 0 0 94 154 0 78-127q1-2 13-23 4-5 6-12l2 0q2 5 6 12l14 23 78 127 143 0 0-94-70 0-103-149 114-165 61 0z m357-121l0-115-287 0-2 15q-2 25-2 26 0 36 15 65t36 48 47 36 47 30 36 30 15 36q0 21-16 35t-39 14q-28 0-54-22-8-6-20-21l-59 51q15 21 35 37 45 36 105 36 61 0 99-33t38-88q0-37-19-66t-47-48-56-35-49-35-23-41l129 0 0 45 70 0z" horiz-adv-x="857.143" />
<glyph glyph-name="inserthorizontalrule" unicode="&#xe818;" d="M214 439l0-107q0-22-16-38t-38-16l-107 0q-22 0-38 16t-16 38l0 107q0 22 16 38t38 16l107 0q22 0 38-16t16-38z m286 0l0-107q0-22-16-38t-38-16l-107 0q-22 0-38 16t-16 38l0 107q0 22 16 38t38 16l107 0q22 0 38-16t16-38z m286 0l0-107q0-22-16-38t-38-16l-107 0q-22 0-38 16t-16 38l0 107q0 22 16 38t38 16l107 0q22 0 38-16t16-38z" horiz-adv-x="785.714" />
<glyph glyph-name="pin" unicode="&#xe812;" d="M268 368l0 250q0 8-5 13t-13 5-13-5-5-13l0-250q0-8 5-13t13-5 13 5 5 13z m375-196q0-15-11-25t-25-11l-239 0-28-270q-1-7-6-11t-11-5l-1 0q-15 0-18 15l-42 271-225 0q-15 0-25 11t-11 25q0 69 44 124t99 55l0 286q-29 0-50 21t-21 50 21 50 50 21l357 0q29 0 50-21t21-50-21-50-50-21l0-286q55 0 99-55t44-124z" horiz-adv-x="642.857" />
<glyph glyph-name="createlink" unicode="&#xe810;" d="M812 171q0 22-16 38l-116 116q-16 16-38 16-23 0-40-18 2-2 11-10t12-12 8-11 7-14 2-15q0-22-16-38t-38-16q-8 0-15 2t-14 7-11 8-12 12-10 11q-18-17-18-41 0-22 16-38l115-116q15-15 38-15 22 0 38 15l82 81q16 16 16 37z m-392 393q0 22-16 38l-115 116q-16 16-38 16t-38-15l-82-81q-16-16-16-37 0-22 16-38l116-116q15-15 38-15t40 17q-2 2-11 10t-12 12-8 11-7 14-2 15q0 22 16 38t38 16q8 0 15-2t14-7 11-8 12-12 10-11q18 17 18 41z m499-393q0-67-47-113l-82-81q-46-46-113-46-68 0-114 47l-115 116q-46 46-46 113 0 69 49 117l-49 49q-48-49-116-49-67 0-114 47l-116 116q-47 47-47 114t47 113l82 81q46 46 113 46 68 0 114-47l115-116q46-46 46-113 0-69-49-117l49-49q48 49 116 49 67 0 114-47l116-116q47-47 47-114z" horiz-adv-x="928.571" />
</font>
</defs>
</svg>
\ No newline at end of file
/*! Licensed under MIT, https://github.com/sofish/pen */
/* basic reset */
.pen, .pen-menu, .pen-input, .pen textarea{font:400 1.16em/1.45 Palatino, Optima, Georgia, serif;color:#331;}
.pen:focus{outline:none;}
.pen fieldset, img {border: 0;}
.pen blockquote{padding-left:10px;margin-left:-14px;border-left:4px solid #1abf89;}
.pen a{color:#1abf89;}
.pen del{text-decoration:line-through;}
.pen sub, .pen sup {font-size:75%;position:relative;vertical-align:text-top;}
:root .pen sub, :root .pen sup{vertical-align:baseline; /* for ie9 and other mordern browsers */}
.pen sup {top:-0.5em;}
.pen sub {bottom:-0.25em;}
.pen hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:25px;*color:pink;*filter:chroma(color=pink);height:10px;*margin:-7px 0 15px;}
.pen small{font-size:0.8em;color:#888;}
.pen em, .pen b, .pen strong{font-weight:700;}
.pen pre{white-space:pre-wrap;padding:0.85em;background:#f8f8f8;}
/* block-level element margin */
.pen p, .pen pre, .pen ul, .pen ol, .pen dl, .pen form, .pen table, .pen blockquote{margin-bottom:16px;}
/* headers */
.pen h1, .pen h2, .pen h3, .pen h4, .pen h5, .pen h6{margin-bottom:16px;font-weight:700;line-height:1.2;}
.pen h1{font-size:2em;}
.pen h2{font-size:1.8em;}
.pen h3{font-size:1.6em;}
.pen h4{font-size:1.4em;}
.pen h5, .pen h6{font-size:1.2em;}
/* list */
.pen ul, .pen ol{margin-left:1.2em;}
.pen ul, .pen-ul{list-style:disc;}
.pen ol, .pen-ol{list-style:decimal;}
.pen li ul, .pen li ol, .pen-ul ul, .pen-ul ol, .pen-ol ul, .pen-ol ol{margin:0 2em 0 1.2em;}
.pen li ul, .pen-ul ul, .pen-ol ul{list-style: circle;}
/* pen menu */
.pen-menu [class^="icon-"], .pen-menu [class*=" icon-"] { /* reset to avoid conflicts with Bootstrap */
background: transparent;
background-image: none;
}
.pen-menu, .pen-input{font-size:14px;line-height:1;}
.pen-menu{white-space:nowrap;box-shadow:1px 2px 3px -2px #222;background:#333;background-image:linear-gradient(to bottom, #222, #333);opacity:0.9;position:fixed;height:36px;border:1px solid #333;border-radius:3px;display:none;z-index:1000;}
.pen-menu:after {top:100%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none;}
.pen-menu:after {border-color:rgba(51, 51, 51, 0);border-top-color:#333;border-width:6px;left:50%;margin-left:-6px;}
.pen-menu-below:after {top: -11px; display:block; -moz-transform: rotate(180deg); -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); transform: rotate(180deg);}
.pen-icon{font:normal 900 16px/40px Georgia serif;min-width:20px;display:inline-block;padding:0 10px;height:36px;overflow:hidden;color:#fff;text-align:center;cursor:pointer;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;user-select:none;}
.pen-icon:first-of-type{border-top-left-radius:3px;border-bottom-left-radius:3px;}
.pen-icon:last-of-type{border-top-right-radius:3px;border-bottom-right-radius:3px;}
.pen-icon:hover{background:#000;}
.pen-icon.active{color:#1abf89;background:#000;box-shadow:inset 2px 2px 4px #000;}
.pen-input{position:absolute;width:100%;left:0;top:0;height:36px;line-height:20px;background:#333;color:#fff;border:none;text-align:center;display:none;font-family:arial, sans-serif;}
.pen-input:focus{outline:none;}
.pen-textarea{display:block;background:#f8f8f8;padding:20px;}
.pen textarea{font-size:14px;border:none;background:none;width:100%;_height:200px;min-height:200px;resize:none;}
@font-face {
font-family: 'pen';
src: url('font/fontello.eot?370dad08');
src: url('font/fontello.eot?370dad08#iefix') format('embedded-opentype'),
url('font/fontello.woff?370dad08') format('woff'),
url('font/fontello.ttf?370dad08') format('truetype'),
url('font/fontello.svg?370dad08#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
.pen-menu [class^="icon-"]:before, .pen-menu [class*=" icon-"]:before {
font-family: "pen";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
font-variant: normal;
text-transform: none;
line-height: 1em;
margin-left: .2em;
}
.pen-menu .icon-location:before { content: '\e815'; } /* '' */
.pen-menu .icon-fit:before { content: '\e80f'; } /* '' */
.pen-menu .icon-bold:before { content: '\e805'; } /* '' */
.pen-menu .icon-italic:before { content: '\e806'; } /* '' */
.pen-menu .icon-justifyleft:before { content: '\e80a'; } /* '' */
.pen-menu .icon-justifycenter:before { content: '\e80b'; } /* '' */
.pen-menu .icon-justifyright:before { content: '\e80c'; } /* '' */
.pen-menu .icon-justifyfull:before { content: '\e80d'; } /* '' */
.pen-menu .icon-outdent:before { content: '\e800'; } /* '' */
.pen-menu .icon-indent:before { content: '\e801'; } /* '' */
.pen-menu .icon-mode:before { content: '\e813'; } /* '' */
.pen-menu .icon-fullscreen:before { content: '\e80e'; } /* '' */
.pen-menu .icon-insertunorderedlist:before { content: '\e802'; } /* '' */
.pen-menu .icon-insertorderedlist:before { content: '\e803'; } /* '' */
.pen-menu .icon-strikethrough:before { content: '\e807'; } /* '' */
.pen-menu .icon-underline:before { content: '\e804'; } /* '' */
.pen-menu .icon-blockquote:before { content: '\e814'; } /* '' */
.pen-menu .icon-undo:before { content: '\e817'; } /* '' */
.pen-menu .icon-code:before { content: '\e816'; } /* '' */
.pen-menu .icon-pre:before { content: '\e816'; } /* '' */
.pen-menu .icon-unlink:before { content: '\e811'; } /* '' */
.pen-menu .icon-superscript:before { content: '\e808'; } /* '' */
.pen-menu .icon-subscript:before { content: '\e809'; } /* '' */
.pen-menu .icon-inserthorizontalrule:before { content: '\e818'; } /* '' */
.pen-menu .icon-pin:before { content: '\e812'; } /* '' */
.pen-menu .icon-createlink:before { content: '\e810'; } /* '' */
.pen-menu .icon-h1:before { content: 'H1'; }
.pen-menu .icon-h2:before { content: 'H2'; }
.pen-menu .icon-h3:before { content: 'H3'; }
.pen-menu .icon-h4:before { content: 'H4'; }
.pen-menu .icon-h5:before { content: 'H5'; }
.pen-menu .icon-h6:before { content: 'H6'; }
.pen-menu .icon-p:before { content: 'P'; }
.pen {
position: relative;
}
.pen.hinted h1:before,
.pen.hinted h2:before,
.pen.hinted h3:before,
.pen.hinted h4:before,
.pen.hinted h5:before,
.pen.hinted h6:before,
.pen.hinted blockquote:before,
.pen.hinted hr:before {
color: #eee;
position: absolute;
right: 100%;
white-space: nowrap;
padding-right: 10px;
}
.pen.hinted blockquote { border-left: 0; margin-left: 0; padding-left: 0; }
.pen.hinted blockquote:before {
color: #1abf89;
content: ">";
font-weight: bold;
vertical-align: center;
}
.pen.hinted h1:before { content: "#";}
.pen.hinted h2:before { content: "##";}
.pen.hinted h3:before { content: "###";}
.pen.hinted h4:before { content: "####";}
.pen.hinted h5:before { content: "#####";}
.pen.hinted h6:before { content: "######";}
.pen.hinted hr:before { content: "﹘﹘﹘"; line-height: 1.2; vertical-align: bottom; }
.pen.hinted pre:before, .pen.hinted pre:after {
content: "```";
display: block;
color: #ccc;
}
.pen.hinted ul { list-style: none; }
.pen.hinted ul li:before {
content: "*";
color: #999;
line-height: 1;
vertical-align: bottom;
margin-left: -1.2em;
display: inline-block;
width: 1.2em;
}
.pen.hinted b:before, .pen.hinted b:after { content: "**"; color: #eee; font-weight: normal; }
.pen.hinted i:before, .pen.hinted i:after { content: "*"; color: #eee; }
.pen.hinted a { text-decoration: none; }
.pen.hinted a:before {content: "["; color: #ddd; }
.pen.hinted a:after { content: "](" attr(href) ")"; color: #ddd; }
.pen-placeholder { color: #999; }
......@@ -3,8 +3,9 @@
<ul>
{{#each tally}}
<li class="poll-result">
<input id="answer-{{key}}" type="radio" disabled {{#if choice}}checked="True"{{/if}} />
<span class="percentage-guage" style="width:{{percentage}}%;"></span>
<span>{{answer}}</span>
<label for="answer-{{key}}">{{answer}}</label>
<span class="poll-percent-display{{#if top}} poll-top-choice{{/if}}">{{percent}}%</span>
</li>
{{/each}}
......
......@@ -3,16 +3,17 @@
{# If no form is present, the Javascript will load the results instead. #}
{% if not choice %}
<form>
<h2>{{ question }}</h2>
<h2>Poll</h2>
{{question|safe}}
<ul>
{% for key, answer in answers %}
<li class="poll-answer">
<label for="answer-{{number}}">{{answer}}</label>
<input type="radio" name="choice" id="answer-{{number}}" value="{{key}}">
<label class="poll-answer" for="answer-{{number}}">{{answer}}</label>
</li>
{% endfor %}
</ul>
<input type="submit" name="poll-submit" onclick="return false;" disabled>
<input type="submit" name="poll-submit" onclick="return false;" value="Submit" disabled>
</form>
{% endif %}
</div>
\ No newline at end of file
......@@ -2,12 +2,21 @@
<div class="wrapper-comp-settings is-active editor-with-buttons" id="settings-tab">
<form id="poll-form">
<ul class="list-input settings-list" id="poll-line-items">
<h2><label for="question-editor">Question/Prompt</label></h2>
Select text to bring up formatting options.
<li class="field comp-setting-entry is-set">
<div class="wrapper-comp-setting">
<label class="label setting-label" for="question">Question</label>
<input class="input setting-input" name="question" id="question" value="{{question}}" type="text" />
<div id="question-editor-container">
<div class="input setting-input" name="question" id="question-editor">{{question|safe}}</div>
</div>
<span class="tip setting-help">Example: 'What is your favorite color?'</span>
</li>
<li class="field comp-setting-entry is-set">
<p>
<strong>Notes:</strong>
If you change an answer's text, all students who voted for that choice will have their votes updated to
the new text. You'll want to avoid changing an answer from something like 'True' to 'False', accordingly.
If you delete an answer, any votes for that answer will also be deleted. Students whose choices are deleted
may vote again, but will not lose course progress.
</p>
</li>
</ul>
<div class="xblock-actions">
......
......@@ -7,6 +7,9 @@ function PollBlock(runtime, element) {
var submit = $('input[type=submit]', element);
var resultsTemplate = Handlebars.compile($("#results", element).html());
function getResults(data) {
if (! data['success']) {
alert(data['errors'].join('\n'));
}
$.ajax({
// Semantically, this would be better as GET, but I can use helper
// functions with POST.
......@@ -44,7 +47,7 @@ function PollBlock(runtime, element) {
enableSubmit();
}
} else {
getResults();
getResults({'success': true});
}
$(function ($) {
......
/* Javascript for ReferenceBlock. */
function PollEditBlock(runtime, element) {
var loadAnswers = runtime.handlerUrl(element, 'load_answers');
var temp = $('#answer-form-component', element).html();
......@@ -40,14 +39,26 @@ function PollEditBlock(runtime, element) {
$(element).find('.save-button').bind('click', function() {
var handlerUrl = runtime.handlerUrl(element, 'studio_submit');
var data = {};
$('#poll-form input', element).each(function() {
var poll_order = []
$('#poll-form input', element).each(function(i) {
data[this.name] = this.value;
if (this.name.indexOf('answer-') >= 0){
poll_order.push(this.name);
}
});
data['poll_order'] = poll_order;
function check_return(data) {
if (data['success']) {
window.location.reload(false);
return;
}
alert(data['errors'].join('\n'));
}
$.ajax({
type: "POST",
url: handlerUrl,
data: JSON.stringify(data),
success: function () {}
success: check_return
});
});
......@@ -58,5 +69,7 @@ function PollEditBlock(runtime, element) {
data: JSON.stringify({}),
success: displayAnswers
});
var pen = new Pen("#question-editor");
$.focus(pen)
});
}
\ No newline at end of file
/*! Licensed under MIT, https://github.com/sofish/pen */
(function(root) {
// only works with Pen
if(!root.Pen) return;
// markdown covertor obj
var covertor = {
keymap: { '96': '`', '62': '>', '49': '1', '46': '.', '45': '-', '42': '*', '35': '#'},
stack : []
};
// return valid markdown syntax
covertor.valid = function(str) {
var len = str.length;
if(str.match(/[#]{1,6}/)) {
return ['h' + len, len];
} else if(str === '```') {
return ['pre', len];
} else if(str === '>') {
return ['blockquote', len];
} else if(str === '1.') {
return ['insertorderedlist', len];
} else if(str === '-' || str === '*') {
return ['insertunorderedlist', len];
} else if(str.match(/(?:\.|\*|\-){3,}/)) {
return ['inserthorizontalrule', len];
}
};
// parse command
covertor.parse = function(e) {
var code = e.keyCode || e.which;
// when `space` is pressed
if(code === 32) {
var cmd = this.stack.join('');
this.stack.length = 0;
return this.valid(cmd);
}
// make cmd
if(this.keymap[code]) this.stack.push(this.keymap[code]);
return false;
};
// exec command
covertor.action = function(pen, cmd) {
// only apply effect at line start
if(pen.selection.focusOffset > cmd[1]) return;
var node = pen.selection.focusNode;
node.textContent = node.textContent.slice(cmd[1]);
pen.execCommand(cmd[0]);
};
// init covertor
covertor.init = function(pen) {
pen.on('keypress', function(e) {
var cmd = covertor.parse(e);
if(cmd) return covertor.action(pen, cmd);
});
};
// append to Pen
root.Pen.prototype.markdown = covertor;
}(window));
/*! Licensed under MIT, https://github.com/sofish/pen */
(function(root, doc) {
var Pen, debugMode, selection, utils = {};
var toString = Object.prototype.toString;
var slice = Array.prototype.slice;
// allow command list
var commandsReg = {
block: /^(?:p|h[1-6]|blockquote|pre)$/,
inline: /^(?:bold|italic|underline|insertorderedlist|insertunorderedlist|indent|outdent)$/,
source: /^(?:insertimage|createlink|unlink)$/,
insert: /^(?:inserthorizontalrule|insert)$/,
wrap: /^(?:code)$/
};
var lineBreakReg = /^(?:blockquote|pre|div)$/i;
var effectNodeReg = /(?:[pubia]|h[1-6]|blockquote|[uo]l|li)/i;
var strReg = {
whiteSpace: /(^\s+)|(\s+$)/g,
mailTo: /^(?!mailto:|.+\/|.+#|.+\?)(.*@.*\..+)$/,
http: /^(?!\w+?:\/\/|mailto:|\/|\.\/|\?|#)(.*)$/
};
var autoLinkReg = {
url: /((https?|ftp):\/\/|www\.)[^\s<]{3,}/gi,
prefix: /^(?:https?|ftp):\/\//i,
notLink: /^(?:img|a|input|audio|video|source|code|pre|script|head|title|style)$/i,
maxLength: 100
};
// type detect
utils.is = function(obj, type) {
return toString.call(obj).slice(8, -1) === type;
};
utils.forEach = function(obj, iterator, arrayLike) {
if (!obj) return;
if (arrayLike == null) arrayLike = utils.is(obj, 'Array');
if (arrayLike) {
for (var i = 0, l = obj.length; i < l; i++) iterator(obj[i], i, obj);
} else {
for (var key in obj) {
if (obj.hasOwnProperty(key)) iterator(obj[key], key, obj);
}
}
};
// copy props from a obj
utils.copy = function(defaults, source) {
utils.forEach(source, function (value, key) {
defaults[key] = utils.is(value, 'Object') ? utils.copy({}, value) :
utils.is(value, 'Array') ? utils.copy([], value) : value;
});
return defaults;
};
// log
utils.log = function(message, force) {
if (debugMode || force)
console.log('%cPEN DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;');
};
utils.delayExec = function (fn) {
var timer = null;
return function (delay) {
clearTimeout(timer);
timer = setTimeout(function() {
fn();
}, delay || 1);
};
};
// merge: make it easy to have a fallback
utils.merge = function(config) {
// default settings
var defaults = {
class: 'pen',
debug: false,
stay: config.stay || !config.debug,
stayMsg: 'Are you going to leave here?',
textarea: '<textarea name="content"></textarea>',
list: [
'blockquote', 'h2', 'h3', 'p', 'code', 'insertorderedlist', 'insertunorderedlist', 'inserthorizontalrule',
'indent', 'outdent', 'bold', 'italic', 'underline', 'createlink'
],
cleanAttrs: ['id', 'class', 'style', 'name'],
cleanTags: ['script']
};
// user-friendly config
if (config.nodeType === 1) {
defaults.editor = config;
} else if (config.match && config.match(/^#[\S]+$/)) {
defaults.editor = doc.getElementById(config.slice(1));
} else {
defaults = utils.copy(defaults, config);
}
return defaults;
};
function commandOverall(ctx, cmd, val) {
var message = ' to exec 「' + cmd + '」 command' + (val ? (' with value: ' + val) : '');
if (doc.execCommand(cmd, false, val)) {
utils.log('success' + message);
} else {
utils.log('fail' + message, true);
}
}
function commandInsert(ctx, name) {
var node = getNode(ctx);
if (!node) return;
ctx._range.selectNode(node);
ctx._range.collapse(false);
return commandOverall(ctx, name);
}
function commandBlock(ctx, name) {
var list = effectNode(ctx, getNode(ctx), true);
if (list.indexOf(name) !== -1) name = 'p';
return commandOverall(ctx, 'formatblock', name);
}
function commandWrap(ctx, tag) {
var val = '<' + tag + '>' + selection + '</' + tag + '>';
return commandOverall(ctx, 'insertHTML', val);
}
// placeholder
function initPlaceholder(ctx) {
var editor = ctx.config.editor;
ctx._placeholder = editor.getAttribute('data-placeholder');
ctx.placeholder();
}
function initToolbar(ctx) {
var icons = '';
utils.forEach(ctx.config.list, function (name) {
var klass = 'pen-icon icon-' + name;
icons += '<i class="' + klass + '" data-action="' + name + '"></i>';
if ((name === 'createlink')) icons += '<input class="pen-input" placeholder="http://" />';
}, true);
ctx._menu = doc.createElement('div');
ctx._menu.setAttribute('class', ctx.config.class + '-menu pen-menu');
ctx._menu.innerHTML = icons;
ctx._menu.style.display = 'none';
doc.body.appendChild(ctx._menu);
}
function initEvents(ctx) {
var menu = ctx._menu, editor = ctx.config.editor;
var setpos = function() {
if (menu.style.display === 'block') ctx.menu();
};
// change menu offset when window resize / scroll
addListener(ctx, window, 'resize', setpos);
addListener(ctx, window, 'scroll', setpos);
var toggleMenu = utils.delayExec(function() {
if (!selection.isCollapsed) {
//show menu
ctx.menu().highlight();
} else {
//hide menu
ctx._menu.style.display = 'none';
}
});
var toggle = function(delay) {
ctx._range = ctx.getRange();
toggleMenu(delay);
};
// toggle toolbar on mouse select
var selecting = false;
addListener(ctx, editor, 'mousedown', function () {
selecting = true;
});
addListener(ctx, editor, 'mouseleave', function () {
if (selecting) toggle(400);
selecting = false;
});
addListener(ctx, editor, 'mouseup', function () {
if (selecting) toggle(0);
selecting = false;
});
// toggle toolbar on key select
addListener(ctx, editor, 'keyup', function (e) {
if (e.which === 8 && ctx.isEmpty()) lineBreak(ctx, true);
else toggle(400);
});
// check line break
addListener(ctx, editor, 'keydown', function (e) {
if (e.which !== 13 || e.shiftKey) return;
var node = getNode(ctx, true);
if (node && lineBreakReg.test(node.nodeName)) {
e.preventDefault();
var p = doc.createElement('p');
p.innerHTML = '<br>';
if (!node.nextSibling) {
editor.appendChild(p);
} else {
editor.insertBefore(p, node.nextSibling);
}
focusNode(ctx, p, ctx.getRange());
}
});
var menuApply = function(action, value) {
ctx.execCommand(action, value);
ctx._range = ctx.getRange();
if (!selection.isCollapsed) ctx.highlight().menu();
};
// toggle toolbar on key select
addListener(ctx, menu, 'click', function(e) {
var action = e.target.getAttribute('data-action');
if (!action) return;
if (action !== 'createlink') return menuApply(action);
// create link
var input = menu.getElementsByTagName('input')[0];
input.style.display = 'block';
input.focus();
var createlink = function(input) {
input.style.display = 'none';
if (input.value) {
var inputValue = input.value
.replace(strReg.whiteSpace, '')
.replace(strReg.mailTo, 'mailto:$1')
.replace(strReg.http, 'http://$1');
return menuApply(action, inputValue);
}
action = 'unlink';
menuApply(action);
};
input.onkeypress = function(e) {
if (e.which === 13) return createlink(e.target);
};
});
// listen for placeholder
addListener(ctx, editor, 'focus', function() {
if (ctx.isEmpty()) lineBreak(ctx, true);
editor.classList.remove('pen-placeholder');
});
addListener(ctx, editor, 'blur', function() {
ctx.placeholder();
ctx.checkContentChange();
});
// listen for paste and clear style
addListener(ctx, editor, 'paste', function() {
setTimeout(function() {
ctx.cleanContent();
});
});
}
function addListener(ctx, target, type, listener) {
if (ctx._events.hasOwnProperty(type)) {
ctx._events[type].push(listener);
} else {
ctx._eventTargets = ctx._eventTargets || [];
ctx._eventsCache = ctx._eventsCache || [];
var index = ctx._eventTargets.indexOf(target);
if (index < 0) index = ctx._eventTargets.push(target) - 1;
ctx._eventsCache[index] = ctx._eventsCache[index] || {};
ctx._eventsCache[index][type] = ctx._eventsCache[index][type] || [];
ctx._eventsCache[index][type].push(listener);
target.addEventListener(type, listener, false);
}
return ctx;
}
// trigger local events
function triggerListener(ctx, type) {
if (!ctx._events.hasOwnProperty(type)) return;
var args = slice.call(arguments, 2);
utils.forEach(ctx._events[type], function (listener) {
listener.apply(ctx, args);
});
}
function removeAllListeners(ctx) {
utils.forEach(this._events, function (events) {
events.length = 0;
}, false);
if (!ctx._eventsCache) return ctx;
utils.forEach(ctx._eventsCache, function (events, index) {
var target = ctx._eventTargets[index];
utils.forEach(events, function (listeners, type) {
utils.forEach(listeners, function (listener) {
target.removeEventListener(type, listener, false);
}, true);
}, false);
}, true);
ctx._eventTargets = [];
ctx._eventsCache = [];
return ctx;
}
// node.contains is not implemented in IE10/IE11
function containsNode(parent, child) {
child = child.parentNode;
while (child) {
if (child === parent) return true;
child = child.parentNode;
}
return false;
}
function getNode(ctx, byRoot) {
var node, root = ctx.config.editor;
ctx._range = ctx.getRange();
node = ctx._range.commonAncestorContainer;
if (!node || node === root) return null;
while (node && (node.nodeType !== 1) && (node.parentNode !== root)) node = node.parentNode;
while (node && byRoot && (node.parentNode !== root)) node = node.parentNode;
return containsNode(root, node) ? node : null;
}
// node effects
function effectNode(ctx, el, returnAsNodeName) {
var nodes = [];
el = el || ctx.config.editor;
while (el !== ctx.config.editor) {
if (el.nodeName.match(effectNodeReg)) {
nodes.push(returnAsNodeName ? el.nodeName.toLowerCase() : el);
}
el = el.parentNode;
}
return nodes;
}
// breakout from node
function lineBreak(ctx, empty) {
var range = ctx._range = ctx.getRange(), node = doc.createElement('p');
if (empty) ctx.config.editor.innerHTML = '';
node.innerHTML = '<br>';
range.insertNode(node);
focusNode(ctx, node.childNodes[0], range);
}
function focusNode(ctx, node, range) {
range.setStartAfter(node);
range.setEndBefore(node);
range.collapse(false);
ctx.setRange(range);
}
function autoLink(node) {
if (node.nodeType === 1) {
if (autoLinkReg.notLink.test(node.tagName)) return;
utils.forEach(node.childNodes, function (child) {
autoLink(child);
}, true);
} else if (node.nodeType === 3) {
var result = urlToLink(node.nodeValue || '');
if (!result.links) return;
var frag = doc.createDocumentFragment(),
div = doc.createElement('div');
div.innerHTML = result.text;
while (div.childNodes.length) frag.appendChild(div.childNodes[0]);
node.parentNode.replaceChild(frag, node);
}
}
function urlToLink(str) {
var count = 0;
str = str.replace(autoLinkReg.url, function(url) {
var realUrl = url, displayUrl = url;
count++;
if (url.length > autoLinkReg.maxLength) displayUrl = url.slice(0, autoLinkReg.maxLength) + '...';
// Add http prefix if necessary
if (!autoLinkReg.prefix.test(realUrl)) realUrl = 'http://' + realUrl;
return '<a href="' + realUrl + '">' + displayUrl + '</a>';
});
return {links: count, text: str};
}
Pen = function(config) {
if (!config) throw new Error('Can\'t find config');
debugMode = config.debug;
// merge user config
var defaults = utils.merge(config);
var editor = defaults.editor;
if (!editor || editor.nodeType !== 1) throw new Error('Can\'t find editor');
// set default class
editor.classList.add(defaults.class);
// set contenteditable
editor.setAttribute('contenteditable', 'true');
// assign config
this.config = defaults;
// save the selection obj
this.selection = selection;
// define local events
this._events = {change: []};
// enable toolbar
initToolbar(this);
// init placeholder
initPlaceholder(this);
// init events
initEvents(this);
// to check content change
this._prevContent = this.getContent();
// enable markdown covert
if (this.markdown) this.markdown.init(this);
// stay on the page
if (this.config.stay) this.stay(this.config);
};
Pen.prototype.on = function(type, listener) {
addListener(this, this.config.editor, type, listener);
return this;
};
Pen.prototype.placeholder = function(placeholder) {
var editor = this.config.editor;
if (placeholder) this._placeholder = placeholder + '';
if (this._placeholder && this.isEmpty()) {
editor.innerHTML = this._placeholder;
editor.classList.add('pen-placeholder');
return true;
}
editor.classList.remove('pen-placeholder');
return false;
};
Pen.prototype.isEmpty = function(node) {
node = node || this.config.editor;
if (node.classList.contains('pen-placeholder')) return true;
var content = node.textContent && node.textContent.trim();
return !content && !node.querySelectorAll('img').length;
};
Pen.prototype.getContent = function() {
return this.isEmpty() ? '' : this.config.editor.innerHTML;
};
Pen.prototype.setContent = function(html) {
this.config.editor.innerHTML = html;
this.cleanContent();
return this;
};
Pen.prototype.checkContentChange = function () {
var prevContent = this._prevContent, currentContent = this.getContent();
if (prevContent === currentContent) return;
this._prevContent = currentContent;
triggerListener(this, 'change', currentContent, prevContent);
};
Pen.prototype.getRange = function() {
var editor = this.config.editor, range = selection.rangeCount && selection.getRangeAt(0);
if (!range) range = doc.createRange();
if (!containsNode(editor, range.commonAncestorContainer)) {
range.selectNodeContents(editor);
range.collapse(false);
}
return range;
};
Pen.prototype.setRange = function(range) {
range = range || this._range;
if (!range) {
range = this.getRange();
range.collapse(false); // set to end
}
selection.removeAllRanges();
selection.addRange(range);
return this;
};
Pen.prototype.focus = function(focusStart) {
if (!focusStart) this.setRange();
this.config.editor.focus();
return this;
};
Pen.prototype.execCommand = function(name, value) {
if (this.isEmpty()) {
this.config.editor.innerHTML = '';
this.config.editor.classList.remove('pen-placeholder');
}
name = name.toLowerCase();
this.setRange();
if (commandsReg.block.test(name)) {
commandBlock(this, name);
} else if (commandsReg.inline.test(name) || commandsReg.source.test(name)) {
commandOverall(this, name, value);
} else if (commandsReg.insert.test(name)) {
commandInsert(this, name);
} else if (commandsReg.wrap.test(name)) {
commandWrap(this, name);
} else {
utils.log('can not find command function for name: ' + name + (value ? (', value: ' + value) : ''), true);
}
if (name === 'indent') this.checkContentChange();
else this.cleanContent({cleanAttrs: ['style']});
};
// remove attrs and tags
// pen.cleanContent({cleanAttrs: ['style'], cleanTags: ['id']})
Pen.prototype.cleanContent = function(options) {
var editor = this.config.editor;
if (!options) options = this.config;
utils.forEach(options.cleanAttrs, function (attr) {
utils.forEach(editor.querySelectorAll('[' + attr + ']'), function(item) {
item.removeAttribute(attr);
}, true);
}, true);
utils.forEach(options.cleanTags, function (tag) {
utils.forEach(editor.querySelectorAll(tag), function(item) {
item.parentNode.removeChild(item);
}, true);
}, true);
this.placeholder();
this.checkContentChange();
return this;
};
// auto link content, return content
Pen.prototype.autoLink = function() {
autoLink(this.config.editor);
return this.getContent();
};
// highlight menu
Pen.prototype.highlight = function() {
var node = selection.focusNode
, effects = effectNode(this, node)
, menu = this._menu
, linkInput = menu.querySelector('input')
, highlight;
// remove all highlights
utils.forEach(menu.querySelectorAll('.active'), function(el) {
el.classList.remove('active');
}, true);
if (linkInput) {
// display link input if createlink enabled
linkInput.style.display = 'none';
// reset link input value
linkInput.value = '';
}
highlight = function(str) {
var selector = '.icon-' + str
, el = menu.querySelector(selector);
return el && el.classList.add('active');
};
utils.forEach(effects, function(item) {
var tag = item.nodeName.toLowerCase();
switch(tag) {
case 'a':
menu.querySelector('input').value = item.getAttribute('href');
tag = 'createlink';
break;
case 'i':
tag = 'italic';
break;
case 'u':
tag = 'underline';
break;
case 'b':
tag = 'bold';
break;
case 'code':
tag = 'code';
break;
case 'ul':
tag = 'insertunorderedlist';
break;
case 'ol':
tag = 'insertorderedlist';
break;
case 'li':
tag = 'indent';
break;
}
highlight(tag);
}, true);
return this;
};
// show menu
Pen.prototype.menu = function() {
var offset = this._range.getBoundingClientRect()
, menuPadding = 10
, top = offset.top - menuPadding
, left = offset.left + (offset.width / 2)
, menu = this._menu
, menuOffset = {x: 0, y: 0}
, stylesheet = this._stylesheet;
// store the stylesheet used for positioning the menu horizontally
if (this._stylesheet === undefined) {
var style = document.createElement("style");
document.head.appendChild(style);
this._stylesheet = stylesheet = style.sheet;
}
// display block to caculate its width & height
menu.style.display = 'block';
menuOffset.x = left - (menu.clientWidth / 2);
menuOffset.y = top - menu.clientHeight;
// check to see if menu has over-extended its bounding box. if it has,
// 1) apply a new class if overflowed on top;
// 2) apply a new rule if overflowed on the left
if (stylesheet.cssRules.length > 0) {
stylesheet.deleteRule(0);
}
if (menuOffset.x < 0) {
menuOffset.x = 0;
stylesheet.insertRule('.pen-menu:after {left: ' + left + 'px;}', 0);
} else {
stylesheet.insertRule('.pen-menu:after {left: 50%; }', 0);
}
if (menuOffset.y < 0) {
menu.classList.add('pen-menu-below');
menuOffset.y = offset.top + offset.height + menuPadding;
} else {
menu.classList.remove('pen-menu-below');
}
menu.style.top = menuOffset.y + 'px';
menu.style.left = menuOffset.x + 'px';
return this;
};
Pen.prototype.stay = function(config) {
var ctx = this;
if (!window.onbeforeunload) {
window.onbeforeunload = function() {
if (!ctx._isDestroyed) return config.stayMsg;
};
}
};
Pen.prototype.destroy = function(isAJoke) {
var destroy = isAJoke ? false : true
, attr = isAJoke ? 'setAttribute' : 'removeAttribute';
if (!isAJoke) {
removeAllListeners(this);
selection.removeAllRanges();
this._menu.parentNode.removeChild(this._menu);
} else {
initToolbar(this);
initPlaceholder(this);
initEvents(this);
}
this._isDestroyed = destroy;
this.config.editor[attr]('contenteditable', '');
return this;
};
Pen.prototype.rebuild = function() {
return this.destroy('it\'s a joke');
};
// a fallback for old browers
root.Pen = function(config) {
if (!config) return utils.log('can\'t find config', true);
var defaults = utils.merge(config)
, klass = defaults.editor.getAttribute('class');
klass = klass ? klass.replace(/\bpen\b/g, '') + ' pen-textarea ' + defaults.class : 'pen pen-textarea';
defaults.editor.setAttribute('class', klass);
defaults.editor.innerHTML = defaults.textarea;
return defaults.editor;
};
// make it accessible
if (doc.getSelection) {
selection = doc.getSelection();
root.Pen = Pen;
}
}(window, document));
-e .
bleach
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment