pen-rails 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0b8a1b2837b2daeb36ef63da63469d020f652d20
4
- data.tar.gz: 28e15c1c472c348d48e4340bf6345007bb0093ef
3
+ metadata.gz: 58260829cf3cdc09f6927e84add1e2528a42c1e0
4
+ data.tar.gz: c92020c137f9cf2785fd2586aeb5951bbb99b86c
5
5
  SHA512:
6
- metadata.gz: d68d0aa300cae75b556f2bd9f117bb36a97ea99068557319d7f9fef5f2a458336286b86060614d0e9fa66db7c96fd57df068fd73cf351866fcf8b516f81cc7f2
7
- data.tar.gz: db5e765c6004fe49f0a9adcca9f793082152c61180eca8ca21910b2c97b97b69db8617e03a81764b1b580bf51a2e87fb75f5d4da77a952a2b9a2cdeebc510445
6
+ metadata.gz: d6fde7231c5276f667f87d190439e75792443d1323e94a90c6df988688518f2a986f02c11be9f2460a98fde15ced7aff1f3ba23dc486b6b4c6966adea7578a7c
7
+ data.tar.gz: 52704d929340b3602df9fda392d9c5d0aaabf2a979b97ba0b03a14ee763c32964f58bf10d0f166d6e3f3b31eb694de15714f407e3a29b47c6e5b6d65ecfd65dd
@@ -0,0 +1,36 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
3
+ <svg xmlns="http://www.w3.org/2000/svg">
4
+ <metadata>Copyright (C) 2012 by original authors @ fontello.com</metadata>
5
+ <defs>
6
+ <font id="fontello" horiz-adv-x="1000" >
7
+ <font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
8
+ <missing-glyph horiz-adv-x="1000" />
9
+ <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" />
10
+ <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" />
11
+ <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" />
12
+ <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" />
13
+ <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" />
14
+ <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" />
15
+ <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" />
16
+ <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" />
17
+ <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" />
18
+ <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" />
19
+ <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" />
20
+ <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" />
21
+ <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" />
22
+ <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" />
23
+ <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" />
24
+ <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" />
25
+ <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" />
26
+ <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" />
27
+ <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" />
28
+ <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" />
29
+ <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" />
30
+ <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" />
31
+ <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" />
32
+ <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" />
33
+ <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" />
34
+ </font>
35
+ </defs>
36
+ </svg>
@@ -0,0 +1,605 @@
1
+ // Backbone.Validation v0.8.0
2
+ //
3
+ // Copyright (c) 2011-2013 Thomas Pedersen
4
+ // Distributed under MIT License
5
+ //
6
+ // Documentation and full license available at:
7
+ // http://thedersen.com/projects/backbone-validation
8
+ Backbone.Validation = (function(_){
9
+ 'use strict';
10
+
11
+ // Default options
12
+ // ---------------
13
+
14
+ var defaultOptions = {
15
+ forceUpdate: false,
16
+ selector: 'name',
17
+ labelFormatter: 'sentenceCase',
18
+ valid: Function.prototype,
19
+ invalid: Function.prototype
20
+ };
21
+
22
+
23
+ // Helper functions
24
+ // ----------------
25
+
26
+ // Formatting functions used for formatting error messages
27
+ var formatFunctions = {
28
+ // Uses the configured label formatter to format the attribute name
29
+ // to make it more readable for the user
30
+ formatLabel: function(attrName, model) {
31
+ return defaultLabelFormatters[defaultOptions.labelFormatter](attrName, model);
32
+ },
33
+
34
+ // Replaces nummeric placeholders like {0} in a string with arguments
35
+ // passed to the function
36
+ format: function() {
37
+ var args = Array.prototype.slice.call(arguments),
38
+ text = args.shift();
39
+ return text.replace(/\{(\d+)\}/g, function(match, number) {
40
+ return typeof args[number] !== 'undefined' ? args[number] : match;
41
+ });
42
+ }
43
+ };
44
+
45
+ // Flattens an object
46
+ // eg:
47
+ //
48
+ // var o = {
49
+ // address: {
50
+ // street: 'Street',
51
+ // zip: 1234
52
+ // }
53
+ // };
54
+ //
55
+ // becomes:
56
+ //
57
+ // var o = {
58
+ // 'address.street': 'Street',
59
+ // 'address.zip': 1234
60
+ // };
61
+ var flatten = function (obj, into, prefix) {
62
+ into = into || {};
63
+ prefix = prefix || '';
64
+
65
+ _.each(obj, function(val, key) {
66
+ if(obj.hasOwnProperty(key)) {
67
+ if (val && typeof val === 'object' && !(
68
+ val instanceof Date ||
69
+ val instanceof RegExp ||
70
+ val instanceof Backbone.Model ||
71
+ val instanceof Backbone.Collection)
72
+ ) {
73
+ flatten(val, into, prefix + key + '.');
74
+ }
75
+ else {
76
+ into[prefix + key] = val;
77
+ }
78
+ }
79
+ });
80
+
81
+ return into;
82
+ };
83
+
84
+ // Validation
85
+ // ----------
86
+
87
+ var Validation = (function(){
88
+
89
+ // Returns an object with undefined properties for all
90
+ // attributes on the model that has defined one or more
91
+ // validation rules.
92
+ var getValidatedAttrs = function(model) {
93
+ return _.reduce(_.keys(model.validation || {}), function(memo, key) {
94
+ memo[key] = void 0;
95
+ return memo;
96
+ }, {});
97
+ };
98
+
99
+ // Looks on the model for validations for a specified
100
+ // attribute. Returns an array of any validators defined,
101
+ // or an empty array if none is defined.
102
+ var getValidators = function(model, attr) {
103
+ var attrValidationSet = model.validation ? model.validation[attr] || {} : {};
104
+
105
+ // If the validator is a function or a string, wrap it in a function validator
106
+ if (_.isFunction(attrValidationSet) || _.isString(attrValidationSet)) {
107
+ attrValidationSet = {
108
+ fn: attrValidationSet
109
+ };
110
+ }
111
+
112
+ // Stick the validator object into an array
113
+ if(!_.isArray(attrValidationSet)) {
114
+ attrValidationSet = [attrValidationSet];
115
+ }
116
+
117
+ // Reduces the array of validators into a new array with objects
118
+ // with a validation method to call, the value to validate against
119
+ // and the specified error message, if any
120
+ return _.reduce(attrValidationSet, function(memo, attrValidation) {
121
+ _.each(_.without(_.keys(attrValidation), 'msg'), function(validator) {
122
+ memo.push({
123
+ fn: defaultValidators[validator],
124
+ val: attrValidation[validator],
125
+ msg: attrValidation.msg
126
+ });
127
+ });
128
+ return memo;
129
+ }, []);
130
+ };
131
+
132
+ // Validates an attribute against all validators defined
133
+ // for that attribute. If one or more errors are found,
134
+ // the first error message is returned.
135
+ // If the attribute is valid, an empty string is returned.
136
+ var validateAttr = function(model, attr, value, computed) {
137
+ // Reduces the array of validators to an error message by
138
+ // applying all the validators and returning the first error
139
+ // message, if any.
140
+ return _.reduce(getValidators(model, attr), function(memo, validator){
141
+ // Pass the format functions plus the default
142
+ // validators as the context to the validator
143
+ var ctx = _.extend({}, formatFunctions, defaultValidators),
144
+ result = validator.fn.call(ctx, value, attr, validator.val, model, computed);
145
+
146
+ if(result === false || memo === false) {
147
+ return false;
148
+ }
149
+ if (result && !memo) {
150
+ return validator.msg || result;
151
+ }
152
+ return memo;
153
+ }, '');
154
+ };
155
+
156
+ // Loops through the model's attributes and validates them all.
157
+ // Returns and object containing names of invalid attributes
158
+ // as well as error messages.
159
+ var validateModel = function(model, attrs) {
160
+ var error,
161
+ invalidAttrs = {},
162
+ isValid = true,
163
+ computed = _.clone(attrs),
164
+ flattened = flatten(attrs);
165
+
166
+ _.each(flattened, function(val, attr) {
167
+ error = validateAttr(model, attr, val, computed);
168
+ if (error) {
169
+ invalidAttrs[attr] = error;
170
+ isValid = false;
171
+ }
172
+ });
173
+
174
+ return {
175
+ invalidAttrs: invalidAttrs,
176
+ isValid: isValid
177
+ };
178
+ };
179
+
180
+ // Contains the methods that are mixed in on the model when binding
181
+ var mixin = function(view, options) {
182
+ return {
183
+
184
+ // Check whether or not a value passes validation
185
+ // without updating the model
186
+ preValidate: function(attr, value) {
187
+ return validateAttr(this, attr, value, _.extend({}, this.attributes));
188
+ },
189
+
190
+ // Check to see if an attribute, an array of attributes or the
191
+ // entire model is valid. Passing true will force a validation
192
+ // of the model.
193
+ isValid: function(option) {
194
+ var flattened = flatten(this.attributes);
195
+
196
+ if(_.isString(option)){
197
+ return !validateAttr(this, option, flattened[option], _.extend({}, this.attributes));
198
+ }
199
+ if(_.isArray(option)){
200
+ return _.reduce(option, function(memo, attr) {
201
+ return memo && !validateAttr(this, attr, flattened[attr], _.extend({}, this.attributes));
202
+ }, true, this);
203
+ }
204
+ if(option === true) {
205
+ this.validate();
206
+ }
207
+ return this.validation ? this._isValid : true;
208
+ },
209
+
210
+ // This is called by Backbone when it needs to perform validation.
211
+ // You can call it manually without any parameters to validate the
212
+ // entire model.
213
+ validate: function(attrs, setOptions){
214
+ var model = this,
215
+ validateAll = !attrs,
216
+ opt = _.extend({}, options, setOptions),
217
+ validatedAttrs = getValidatedAttrs(model),
218
+ allAttrs = _.extend({}, validatedAttrs, model.attributes, attrs),
219
+ changedAttrs = flatten(attrs || allAttrs),
220
+
221
+ result = validateModel(model, allAttrs);
222
+
223
+ model._isValid = result.isValid;
224
+
225
+ // After validation is performed, loop through all changed attributes
226
+ // and call the valid callbacks so the view is updated.
227
+ _.each(validatedAttrs, function(val, attr){
228
+ var invalid = result.invalidAttrs.hasOwnProperty(attr);
229
+ if(!invalid){
230
+ opt.valid(view, attr, opt.selector);
231
+ }
232
+ });
233
+
234
+ // After validation is performed, loop through all changed attributes
235
+ // and call the invalid callback so the view is updated.
236
+ _.each(validatedAttrs, function(val, attr){
237
+ var invalid = result.invalidAttrs.hasOwnProperty(attr),
238
+ changed = changedAttrs.hasOwnProperty(attr);
239
+
240
+ if(invalid && (changed || validateAll)){
241
+ opt.invalid(view, attr, result.invalidAttrs[attr], opt.selector);
242
+ }
243
+ });
244
+
245
+ // Trigger validated events.
246
+ // Need to defer this so the model is actually updated before
247
+ // the event is triggered.
248
+ _.defer(function() {
249
+ model.trigger('validated', model._isValid, model, result.invalidAttrs);
250
+ model.trigger('validated:' + (model._isValid ? 'valid' : 'invalid'), model, result.invalidAttrs);
251
+ });
252
+
253
+ // Return any error messages to Backbone, unless the forceUpdate flag is set.
254
+ // Then we do not return anything and fools Backbone to believe the validation was
255
+ // a success. That way Backbone will update the model regardless.
256
+ if (!opt.forceUpdate && _.intersection(_.keys(result.invalidAttrs), _.keys(changedAttrs)).length > 0) {
257
+ return result.invalidAttrs;
258
+ }
259
+ }
260
+ };
261
+ };
262
+
263
+ // Helper to mix in validation on a model
264
+ var bindModel = function(view, model, options) {
265
+ _.extend(model, mixin(view, options));
266
+ };
267
+
268
+ // Removes the methods added to a model
269
+ var unbindModel = function(model) {
270
+ delete model.validate;
271
+ delete model.preValidate;
272
+ delete model.isValid;
273
+ };
274
+
275
+ // Mix in validation on a model whenever a model is
276
+ // added to a collection
277
+ var collectionAdd = function(model) {
278
+ bindModel(this.view, model, this.options);
279
+ };
280
+
281
+ // Remove validation from a model whenever a model is
282
+ // removed from a collection
283
+ var collectionRemove = function(model) {
284
+ unbindModel(model);
285
+ };
286
+
287
+ // Returns the public methods on Backbone.Validation
288
+ return {
289
+
290
+ // Current version of the library
291
+ version: '0.8.0',
292
+
293
+ // Called to configure the default options
294
+ configure: function(options) {
295
+ _.extend(defaultOptions, options);
296
+ },
297
+
298
+ // Hooks up validation on a view with a model
299
+ // or collection
300
+ bind: function(view, options) {
301
+ var model = view.model,
302
+ collection = view.collection;
303
+
304
+ options = _.extend({}, defaultOptions, defaultCallbacks, options);
305
+
306
+ if(typeof model === 'undefined' && typeof collection === 'undefined'){
307
+ throw 'Before you execute the binding your view must have a model or a collection.\n' +
308
+ 'See http://thedersen.com/projects/backbone-validation/#using-form-model-validation for more information.';
309
+ }
310
+
311
+ if(model) {
312
+ bindModel(view, model, options);
313
+ }
314
+ else if(collection) {
315
+ collection.each(function(model){
316
+ bindModel(view, model, options);
317
+ });
318
+ collection.bind('add', collectionAdd, {view: view, options: options});
319
+ collection.bind('remove', collectionRemove);
320
+ }
321
+ },
322
+
323
+ // Removes validation from a view with a model
324
+ // or collection
325
+ unbind: function(view) {
326
+ var model = view.model,
327
+ collection = view.collection;
328
+
329
+ if(model) {
330
+ unbindModel(view.model);
331
+ }
332
+ if(collection) {
333
+ collection.each(function(model){
334
+ unbindModel(model);
335
+ });
336
+ collection.unbind('add', collectionAdd);
337
+ collection.unbind('remove', collectionRemove);
338
+ }
339
+ },
340
+
341
+ // Used to extend the Backbone.Model.prototype
342
+ // with validation
343
+ mixin: mixin(null, defaultOptions)
344
+ };
345
+ }());
346
+
347
+
348
+ // Callbacks
349
+ // ---------
350
+
351
+ var defaultCallbacks = Validation.callbacks = {
352
+
353
+ // Gets called when a previously invalid field in the
354
+ // view becomes valid. Removes any error message.
355
+ // Should be overridden with custom functionality.
356
+ valid: function(view, attr, selector) {
357
+ view.$('[' + selector + '~="' + attr + '"]')
358
+ .removeClass('invalid')
359
+ .removeAttr('data-error');
360
+ },
361
+
362
+ // Gets called when a field in the view becomes invalid.
363
+ // Adds a error message.
364
+ // Should be overridden with custom functionality.
365
+ invalid: function(view, attr, error, selector) {
366
+ view.$('[' + selector + '~="' + attr + '"]')
367
+ .addClass('invalid')
368
+ .attr('data-error', error);
369
+ }
370
+ };
371
+
372
+
373
+ // Patterns
374
+ // --------
375
+
376
+ var defaultPatterns = Validation.patterns = {
377
+ // Matches any digit(s) (i.e. 0-9)
378
+ digits: /^\d+$/,
379
+
380
+ // Matched any number (e.g. 100.000)
381
+ number: /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/,
382
+
383
+ // Matches a valid email address (e.g. mail@example.com)
384
+ email: /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i,
385
+
386
+ // Mathes any valid url (e.g. http://www.xample.com)
387
+ url: /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
388
+ };
389
+
390
+
391
+ // Error messages
392
+ // --------------
393
+
394
+ // Error message for the build in validators.
395
+ // {x} gets swapped out with arguments form the validator.
396
+ var defaultMessages = Validation.messages = {
397
+ required: '{0} is required',
398
+ acceptance: '{0} must be accepted',
399
+ min: '{0} must be greater than or equal to {1}',
400
+ max: '{0} must be less than or equal to {1}',
401
+ range: '{0} must be between {1} and {2}',
402
+ length: '{0} must be {1} characters',
403
+ minLength: '{0} must be at least {1} characters',
404
+ maxLength: '{0} must be at most {1} characters',
405
+ rangeLength: '{0} must be between {1} and {2} characters',
406
+ oneOf: '{0} must be one of: {1}',
407
+ equalTo: '{0} must be the same as {1}',
408
+ pattern: '{0} must be a valid {1}'
409
+ };
410
+
411
+ // Label formatters
412
+ // ----------------
413
+
414
+ // Label formatters are used to convert the attribute name
415
+ // to a more human friendly label when using the built in
416
+ // error messages.
417
+ // Configure which one to use with a call to
418
+ //
419
+ // Backbone.Validation.configure({
420
+ // labelFormatter: 'label'
421
+ // });
422
+ var defaultLabelFormatters = Validation.labelFormatters = {
423
+
424
+ // Returns the attribute name with applying any formatting
425
+ none: function(attrName) {
426
+ return attrName;
427
+ },
428
+
429
+ // Converts attributeName or attribute_name to Attribute name
430
+ sentenceCase: function(attrName) {
431
+ return attrName.replace(/(?:^\w|[A-Z]|\b\w)/g, function(match, index) {
432
+ return index === 0 ? match.toUpperCase() : ' ' + match.toLowerCase();
433
+ }).replace('_', ' ');
434
+ },
435
+
436
+ // Looks for a label configured on the model and returns it
437
+ //
438
+ // var Model = Backbone.Model.extend({
439
+ // validation: {
440
+ // someAttribute: {
441
+ // required: true
442
+ // }
443
+ // },
444
+ //
445
+ // labels: {
446
+ // someAttribute: 'Custom label'
447
+ // }
448
+ // });
449
+ label: function(attrName, model) {
450
+ return (model.labels && model.labels[attrName]) || defaultLabelFormatters.sentenceCase(attrName, model);
451
+ }
452
+ };
453
+
454
+
455
+ // Built in validators
456
+ // -------------------
457
+
458
+ var defaultValidators = Validation.validators = (function(){
459
+ // Use native trim when defined
460
+ var trim = String.prototype.trim ?
461
+ function(text) {
462
+ return text === null ? '' : String.prototype.trim.call(text);
463
+ } :
464
+ function(text) {
465
+ var trimLeft = /^\s+/,
466
+ trimRight = /\s+$/;
467
+
468
+ return text === null ? '' : text.toString().replace(trimLeft, '').replace(trimRight, '');
469
+ };
470
+
471
+ // Determines whether or not a value is a number
472
+ var isNumber = function(value){
473
+ return _.isNumber(value) || (_.isString(value) && value.match(defaultPatterns.number));
474
+ };
475
+
476
+ // Determines whether or not not a value is empty
477
+ var hasValue = function(value) {
478
+ return !(_.isNull(value) || _.isUndefined(value) || (_.isString(value) && trim(value) === ''));
479
+ };
480
+
481
+ return {
482
+ // Function validator
483
+ // Lets you implement a custom function used for validation
484
+ fn: function(value, attr, fn, model, computed) {
485
+ if(_.isString(fn)){
486
+ fn = model[fn];
487
+ }
488
+ return fn.call(model, value, attr, computed);
489
+ },
490
+
491
+ // Required validator
492
+ // Validates if the attribute is required or not
493
+ required: function(value, attr, required, model, computed) {
494
+ var isRequired = _.isFunction(required) ? required.call(model, value, attr, computed) : required;
495
+ if(!isRequired && !hasValue(value)) {
496
+ return false; // overrides all other validators
497
+ }
498
+ if (isRequired && !hasValue(value)) {
499
+ return this.format(defaultMessages.required, this.formatLabel(attr, model));
500
+ }
501
+ },
502
+
503
+ // Acceptance validator
504
+ // Validates that something has to be accepted, e.g. terms of use
505
+ // `true` or 'true' are valid
506
+ acceptance: function(value, attr, accept, model) {
507
+ if(value !== 'true' && (!_.isBoolean(value) || value === false)) {
508
+ return this.format(defaultMessages.acceptance, this.formatLabel(attr, model));
509
+ }
510
+ },
511
+
512
+ // Min validator
513
+ // Validates that the value has to be a number and equal to or greater than
514
+ // the min value specified
515
+ min: function(value, attr, minValue, model) {
516
+ if (!isNumber(value) || value < minValue) {
517
+ return this.format(defaultMessages.min, this.formatLabel(attr, model), minValue);
518
+ }
519
+ },
520
+
521
+ // Max validator
522
+ // Validates that the value has to be a number and equal to or less than
523
+ // the max value specified
524
+ max: function(value, attr, maxValue, model) {
525
+ if (!isNumber(value) || value > maxValue) {
526
+ return this.format(defaultMessages.max, this.formatLabel(attr, model), maxValue);
527
+ }
528
+ },
529
+
530
+ // Range validator
531
+ // Validates that the value has to be a number and equal to or between
532
+ // the two numbers specified
533
+ range: function(value, attr, range, model) {
534
+ if(!isNumber(value) || value < range[0] || value > range[1]) {
535
+ return this.format(defaultMessages.range, this.formatLabel(attr, model), range[0], range[1]);
536
+ }
537
+ },
538
+
539
+ // Length validator
540
+ // Validates that the value has to be a string with length equal to
541
+ // the length value specified
542
+ length: function(value, attr, length, model) {
543
+ if (!hasValue(value) || trim(value).length !== length) {
544
+ return this.format(defaultMessages.length, this.formatLabel(attr, model), length);
545
+ }
546
+ },
547
+
548
+ // Min length validator
549
+ // Validates that the value has to be a string with length equal to or greater than
550
+ // the min length value specified
551
+ minLength: function(value, attr, minLength, model) {
552
+ if (!hasValue(value) || trim(value).length < minLength) {
553
+ return this.format(defaultMessages.minLength, this.formatLabel(attr, model), minLength);
554
+ }
555
+ },
556
+
557
+ // Max length validator
558
+ // Validates that the value has to be a string with length equal to or less than
559
+ // the max length value specified
560
+ maxLength: function(value, attr, maxLength, model) {
561
+ if (!hasValue(value) || trim(value).length > maxLength) {
562
+ return this.format(defaultMessages.maxLength, this.formatLabel(attr, model), maxLength);
563
+ }
564
+ },
565
+
566
+ // Range length validator
567
+ // Validates that the value has to be a string and equal to or between
568
+ // the two numbers specified
569
+ rangeLength: function(value, attr, range, model) {
570
+ if(!hasValue(value) || trim(value).length < range[0] || trim(value).length > range[1]) {
571
+ return this.format(defaultMessages.rangeLength, this.formatLabel(attr, model), range[0], range[1]);
572
+ }
573
+ },
574
+
575
+ // One of validator
576
+ // Validates that the value has to be equal to one of the elements in
577
+ // the specified array. Case sensitive matching
578
+ oneOf: function(value, attr, values, model) {
579
+ if(!_.include(values, value)){
580
+ return this.format(defaultMessages.oneOf, this.formatLabel(attr, model), values.join(', '));
581
+ }
582
+ },
583
+
584
+ // Equal to validator
585
+ // Validates that the value has to be equal to the value of the attribute
586
+ // with the name specified
587
+ equalTo: function(value, attr, equalTo, model, computed) {
588
+ if(value !== computed[equalTo]) {
589
+ return this.format(defaultMessages.equalTo, this.formatLabel(attr, model), this.formatLabel(equalTo, model));
590
+ }
591
+ },
592
+
593
+ // Pattern validator
594
+ // Validates that the value has to match the pattern specified.
595
+ // Can be a regular expression or the name of one of the built in patterns
596
+ pattern: function(value, attr, pattern, model) {
597
+ if (!hasValue(value) || !value.toString().match(defaultPatterns[pattern] || pattern)) {
598
+ return this.format(defaultMessages.pattern, this.formatLabel(attr, model), pattern);
599
+ }
600
+ }
601
+ };
602
+ }());
603
+
604
+ return Validation;
605
+ }(_));
@@ -0,0 +1,72 @@
1
+ /*! Licensed under MIT, https://github.com/sofish/pen */
2
+ (function() {
3
+
4
+ // only works with Pen
5
+ if(!this.Pen) return;
6
+
7
+ // markdown covertor obj
8
+ var covertor = {
9
+ keymap: { '96': '`', '62': '>', '49': '1', '46': '.', '45': '-', '42': '*', '35': '#'},
10
+ stack : []
11
+ };
12
+
13
+ // return valid markdown syntax
14
+ covertor.valid = function(str) {
15
+ var len = str.length;
16
+
17
+ if(str.match(/[#]{1,6}/)) {
18
+ return ['h' + len, len];
19
+ } else if(str === '```') {
20
+ return ['pre', len];
21
+ } else if(str === '>') {
22
+ return ['blockquote', len];
23
+ } else if(str === '1.') {
24
+ return ['insertorderedlist', len];
25
+ } else if(str === '-' || str === '*') {
26
+ return ['insertunorderedlist', len];
27
+ } else if(str.match(/(?:\.|\*|\-){3,}/)) {
28
+ return ['inserthorizontalrule', len];
29
+ }
30
+ };
31
+
32
+ // parse command
33
+ covertor.parse = function(e) {
34
+ var code = e.keyCode || e.which;
35
+
36
+ // when `space` is pressed
37
+ if(code === 32) {
38
+ var cmd = this.stack.join('');
39
+ this.stack.length = 0;
40
+ return this.valid(cmd);
41
+ }
42
+
43
+ // make cmd
44
+ if(this.keymap[code]) this.stack.push(this.keymap[code]);
45
+
46
+ return false;
47
+ };
48
+
49
+ // exec command
50
+ covertor.action = function(pen, cmd) {
51
+
52
+ // only apply effect at line start
53
+ if(pen._sel.focusOffset > cmd[1]) return;
54
+
55
+ var node = pen._sel.focusNode;
56
+ node.textContent = node.textContent.slice(cmd[1]);
57
+ pen._actions(cmd[0]);
58
+ pen.nostyle();
59
+ };
60
+
61
+ // init covertor
62
+ covertor.init = function(pen) {
63
+ pen.config.editor.addEventListener('keypress', function(e) {
64
+ var cmd = covertor.parse(e);
65
+ if(cmd) return covertor.action(pen, cmd);
66
+ });
67
+ };
68
+
69
+ // append to Pen
70
+ window.Pen.prototype.markdown = covertor;
71
+
72
+ }());
@@ -0,0 +1,368 @@
1
+ /*! Licensed under MIT, https://github.com/sofish/pen */
2
+ /* jshint -W030, -W093, -W015 */
3
+ (function(doc) {
4
+
5
+ var Pen, FakePen, utils = {};
6
+
7
+ // type detect
8
+ utils.is = function(obj, type) {
9
+ return Object.prototype.toString.call(obj).slice(8, -1) === type;
10
+ };
11
+
12
+ // copy props from a obj
13
+ utils.copy = function(defaults, source) {
14
+ for(var p in source) {
15
+ if(source.hasOwnProperty(p)) {
16
+ var val = source[p];
17
+ defaults[p] = this.is(val, 'Object') ? this.copy({}, val) :
18
+ this.is(val, 'Array') ? this.copy([], val) : val;
19
+ }
20
+ }
21
+ return defaults;
22
+ };
23
+
24
+ // log
25
+ utils.log = function(message, force) {
26
+ if(window._pen_debug_mode_on || force) console.log('%cPEN DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;');
27
+ };
28
+
29
+ // shift a function
30
+ utils.shift = function(key, fn, time) {
31
+ time = time || 50;
32
+ var queue = this['_shift_fn' + key], timeout = 'shift_timeout' + key, current;
33
+ queue ? queue.concat([fn, time]) : (queue = [[fn, time]]);
34
+ current = queue.pop();
35
+ clearTimeout(this[timeout]);
36
+ this[timeout] = setTimeout(function() {
37
+ current[0]();
38
+ }, time);
39
+ };
40
+
41
+ // merge: make it easy to have a fallback
42
+ utils.merge = function(config) {
43
+
44
+ // default settings
45
+ var defaults = {
46
+ class: 'pen',
47
+ debug: false,
48
+ stay: config.stay || !config.debug,
49
+ textarea: '<textarea name="content"></textarea>',
50
+ list: [
51
+ 'blockquote', 'h2', 'h3', 'p', 'insertorderedlist', 'insertunorderedlist', 'inserthorizontalrule',
52
+ 'indent', 'outdent', 'bold', 'italic', 'underline', 'createlink'
53
+ ]
54
+ };
55
+
56
+ // user-friendly config
57
+ if(config.nodeType === 1) {
58
+ defaults.editor = config;
59
+ } else if(config.match && config.match(/^#[\S]+$/)) {
60
+ defaults.editor = document.getElementById(config.slice(1));
61
+ } else {
62
+ defaults = utils.copy(defaults, config);
63
+ }
64
+
65
+ return defaults;
66
+ };
67
+
68
+ Pen = function(config) {
69
+
70
+ if(!config) return utils.log('can\'t find config', true);
71
+
72
+ // merge user config
73
+ var defaults = utils.merge(config);
74
+
75
+ if(defaults.editor.nodeType !== 1) return utils.log('can\'t find editor');
76
+ if(defaults.debug) window._pen_debug_mode_on = true;
77
+
78
+ var editor = defaults.editor;
79
+
80
+ // set default class
81
+ editor.classList.add(defaults.class);
82
+
83
+ // set contenteditable
84
+ var editable = editor.getAttribute('contenteditable');
85
+ if(!editable) editor.setAttribute('contenteditable', 'true');
86
+
87
+ // assign config
88
+ this.config = defaults;
89
+
90
+ // save the selection obj
91
+ this._sel = doc.getSelection();
92
+
93
+ // map actions
94
+ this.actions();
95
+
96
+ // enable toolbar
97
+ this.toolbar();
98
+
99
+ // enable markdown covert
100
+ this.markdown && this.markdown.init(this);
101
+
102
+ // stay on the page
103
+ this.config.stay && this.stay();
104
+ };
105
+
106
+ // node effects
107
+ Pen.prototype._effectNode = function(el, returnAsNodeName) {
108
+ var nodes = [];
109
+ while(el !== this.config.editor) {
110
+ if(el.nodeName.match(/(?:[pubia]|h[1-6]|blockquote|[uo]l|li)/i)) {
111
+ nodes.push(returnAsNodeName ? el.nodeName.toLowerCase() : el);
112
+ }
113
+ el = el.parentNode;
114
+ }
115
+ return nodes;
116
+ };
117
+
118
+ // remove style attr
119
+ Pen.prototype.nostyle = function() {
120
+ var els = this.config.editor.querySelectorAll('[style]');
121
+ [].slice.call(els).forEach(function(item) {
122
+ item.removeAttribute('style');
123
+ });
124
+ return this;
125
+ };
126
+
127
+ Pen.prototype.toolbar = function() {
128
+
129
+ var that = this, icons = '';
130
+
131
+ for(var i = 0, list = this.config.list; i < list.length; i++) {
132
+ var name = list[i], klass = 'pen-icon icon-' + name;
133
+ icons += '<i class="' + klass + '" data-action="' + name + '">' + (name.match(/^h[1-6]|p$/i) ? name.toUpperCase() : '') + '</i>';
134
+ if((name === 'createlink')) icons += '<input class="pen-input" placeholder="http://" />';
135
+ }
136
+
137
+ var menu = doc.createElement('div');
138
+ menu.setAttribute('class', this.config.class + '-menu pen-menu');
139
+ menu.innerHTML = icons;
140
+ menu.style.display = 'none';
141
+
142
+ doc.body.appendChild((this._menu = menu));
143
+
144
+ var setpos = function() {
145
+ if(menu.style.display === 'block') that.menu();
146
+ };
147
+
148
+ // change menu offset when window resize / scroll
149
+ window.addEventListener('resize', setpos);
150
+ window.addEventListener('scroll', setpos);
151
+
152
+ var editor = this.config.editor;
153
+ var toggle = function() {
154
+
155
+ if(that._isDestroyed) return;
156
+
157
+ utils.shift('toggle_menu', function() {
158
+ var range = that._sel;
159
+ if(!range.isCollapsed) {
160
+ //show menu
161
+ that._range = range.getRangeAt(0);
162
+ that.menu().highlight();
163
+ } else {
164
+ //hide menu
165
+ that._menu.style.display = 'none';
166
+ }
167
+ }, 200);
168
+ };
169
+
170
+ // toggle toolbar on mouse select
171
+ editor.addEventListener('mouseup', toggle);
172
+
173
+ // toggle toolbar on key select
174
+ editor.addEventListener('keyup', toggle);
175
+
176
+ // toggle toolbar on key select
177
+ menu.addEventListener('click', function(e) {
178
+ var action = e.target.getAttribute('data-action');
179
+
180
+ if(!action) return;
181
+
182
+ var apply = function(value) {
183
+ that._sel.removeAllRanges();
184
+ that._sel.addRange(that._range);
185
+ that._actions(action, value);
186
+ that._range = that._sel.getRangeAt(0);
187
+ that.highlight().nostyle().menu();
188
+ };
189
+
190
+ // create link
191
+ if(action === 'createlink') {
192
+ var input = menu.getElementsByTagName('input')[0], createlink;
193
+
194
+ input.style.display = 'block';
195
+ input.focus();
196
+
197
+ createlink = function(input) {
198
+ input.style.display = 'none';
199
+ if(input.value) return apply(input.value.replace(/(^\s+)|(\s+$)/g, '').replace(/^(?!http:\/\/|https:\/\/)(.*)$/, 'http://$1'));
200
+ action = 'unlink';
201
+ apply();
202
+ };
203
+
204
+ return input.onkeypress = function(e) {
205
+ if(e.which === 13) return createlink(e.target);
206
+ };
207
+ }
208
+
209
+ apply();
210
+ });
211
+
212
+ return this;
213
+ };
214
+
215
+ // highlight menu
216
+ Pen.prototype.highlight = function() {
217
+ var node = this._sel.focusNode
218
+ , effects = this._effectNode(node)
219
+ , menu = this._menu
220
+ , linkInput = menu.querySelector('input')
221
+ , highlight;
222
+
223
+ // remove all highlights
224
+ [].slice.call(menu.querySelectorAll('.active')).forEach(function(el) {
225
+ el.classList.remove('active');
226
+ });
227
+
228
+ // display link input if createlink enabled
229
+ if (linkInput) linkInput.style.display = 'none';
230
+
231
+ highlight = function(str) {
232
+ var selector = '.icon-' + str
233
+ , el = menu.querySelector(selector);
234
+ return el && el.classList.add('active');
235
+ };
236
+
237
+ effects.forEach(function(item) {
238
+ var tag = item.nodeName.toLowerCase();
239
+ switch(tag) {
240
+ case 'a': return (menu.querySelector('input').value = item.href), highlight('createlink');
241
+ case 'i': return highlight('italic');
242
+ case 'u': return highlight('underline');
243
+ case 'b': return highlight('bold');
244
+ case 'ul': return highlight('insertunorderedlist');
245
+ case 'ol': return highlight('insertorderedlist');
246
+ case 'ol': return highlight('insertorderedlist');
247
+ case 'li': return highlight('indent');
248
+ default : highlight(tag);
249
+ }
250
+ });
251
+
252
+ return this;
253
+ };
254
+
255
+ Pen.prototype.actions = function() {
256
+ var that = this, reg, block, overall, insert;
257
+
258
+ // allow command list
259
+ reg = {
260
+ block: /^(?:p|h[1-6]|blockquote|pre)$/,
261
+ inline: /^(?:bold|italic|underline|insertorderedlist|insertunorderedlist|indent|outdent)$/,
262
+ source: /^(?:insertimage|createlink|unlink)$/,
263
+ insert: /^(?:inserthorizontalrule|insert)$/
264
+ };
265
+
266
+ overall = function(cmd, val) {
267
+ var message = ' to exec 「' + cmd + '」 command' + (val ? (' with value: ' + val) : '');
268
+ if(document.execCommand(cmd, false, val) && that.config.debug) {
269
+ utils.log('success' + message);
270
+ } else {
271
+ utils.log('fail' + message);
272
+ }
273
+ };
274
+
275
+ insert = function(name) {
276
+ var range = that._sel.getRangeAt(0)
277
+ , node = range.startContainer;
278
+
279
+ while(node.nodeType !== 1) {
280
+ node = node.parentNode;
281
+ }
282
+
283
+ range.selectNode(node);
284
+ range.collapse(false);
285
+ return overall(name);
286
+ };
287
+
288
+ block = function(name) {
289
+ if(that._effectNode(that._sel.getRangeAt(0).startContainer, true).indexOf(name) !== -1) {
290
+ if(name === 'blockquote') return document.execCommand('outdent', false, null);
291
+ name = 'p';
292
+ }
293
+ return overall('formatblock', name);
294
+ };
295
+
296
+ this._actions = function(name, value) {
297
+ if(name.match(reg.block)) {
298
+ block(name);
299
+ } else if(name.match(reg.inline) || name.match(reg.source)) {
300
+ overall(name, value);
301
+ } else if(name.match(reg.insert)) {
302
+ insert(name);
303
+ } else {
304
+ if(this.config.debug) utils.log('can not find command function for name: ' + name + (value ? (', value: ' + value) : ''));
305
+ }
306
+ };
307
+
308
+ return this;
309
+ };
310
+
311
+ // show menu
312
+ Pen.prototype.menu = function() {
313
+
314
+ var offset = this._range.getBoundingClientRect()
315
+ , top = offset.top - 10
316
+ , left = offset.left + (offset.width / 2)
317
+ , menu = this._menu;
318
+
319
+ // display block to caculate it's width & height
320
+ menu.style.display = 'block';
321
+ menu.style.top = top - menu.clientHeight + 'px';
322
+ menu.style.left = left - (menu.clientWidth/2) + 'px';
323
+
324
+ return this;
325
+ };
326
+
327
+ Pen.prototype.stay = function() {
328
+ var that = this;
329
+ !window.onbeforeunload && (window.onbeforeunload = function() {
330
+ if(!that._isDestroyed) return 'Are you going to leave here?';
331
+ });
332
+ };
333
+
334
+ Pen.prototype.destroy = function(isAJoke) {
335
+ var destroy = isAJoke ? false : true
336
+ , attr = isAJoke ? 'setAttribute' : 'removeAttribute';
337
+
338
+ if(!isAJoke) {
339
+ this._sel.removeAllRanges();
340
+ this._menu.style.display = 'none';
341
+ }
342
+ this._isDestroyed = destroy;
343
+ this.config.editor[attr]('contenteditable', '');
344
+
345
+ return this;
346
+ };
347
+
348
+ Pen.prototype.rebuild = function() {
349
+ return this.destroy('it\'s a joke');
350
+ };
351
+
352
+ // a fallback for old browers
353
+ FakePen = function(config) {
354
+ if(!config) return utils.log('can\'t find config', true);
355
+
356
+ var defaults = utils.merge(config)
357
+ , klass = defaults.editor.getAttribute('class');
358
+
359
+ klass = klass ? klass.replace(/\bpen\b/g, '') + ' pen-textarea ' + defaults.class : 'pen pen-textarea';
360
+ defaults.editor.setAttribute('class', klass);
361
+ defaults.editor.innerHTML = defaults.textarea;
362
+ return defaults.editor;
363
+ };
364
+
365
+ // make it accessible
366
+ this.Pen = doc.getSelection ? Pen : FakePen;
367
+
368
+ }(document));
@@ -0,0 +1,164 @@
1
+ /*! Licensed under MIT, https://github.com/sofish/pen */
2
+ /* FONT PATH
3
+ /* basic reset */
4
+ //= depend_on_asset "fontello.eot"
5
+ //= depend_on_asset "fontello.svg"
6
+ //= depend_on_asset "fontello.ttf"
7
+ //= depend_on_asset "fontello.woff"
8
+ .pen, .pen-menu, .pen-input, .pen textarea{font:400 1.16em/1.45 Palatino, Optima, Georgia, serif;color:#331;}
9
+ .pen:focus{outline:none;}
10
+ .pen fieldset, img {border: 0;}
11
+ .pen blockquote{padding-left:10px;margin-left:-14px;border-left:4px solid #1abf89;}
12
+ .pen a{color:#1abf89;}
13
+ .pen del{text-decoration:line-through;}
14
+ .pen sub, .pen sup {font-size:75%;position:relative;vertical-align:text-top\9;}
15
+ :root .pen sub, :root .pen sup{vertical-align:baseline; /* for ie9 and other mordern browsers */}
16
+ .pen sup {top:-0.5em;}
17
+ .pen sub {bottom:-0.25em;}
18
+ .pen hr{border:none;border-bottom:1px solid #cfcfcf;margin-bottom:25px;*color:pink;*filter:chroma(color=pink);height:10px;*margin:-7px 0 15px;}
19
+ .pen small{font-size:0.8em;color:#888;}
20
+ .pen em, .pen b, .pen strong{font-weight:700;}
21
+ .pen pre{white-space:pre-wrap;padding:0.85em;background:#f8f8f8;}
22
+
23
+ /* block-level element margin */
24
+ .pen p, .pen pre, .pen ul, .pen ol, .pen dl, .pen form, .pen table, .pen blockquote{margin-bottom:16px;}
25
+
26
+ /* headers */
27
+ .pen h1, .pen h2, .pen h3, .pen h4, .pen h5, .pen h6{margin-bottom:16px;font-weight:700;line-height:1.2;}
28
+ .pen h1{font-size:2em;}
29
+ .pen h2{font-size:1.8em;}
30
+ .pen h3{font-size:1.6em;}
31
+ .pen h4{font-size:1.4em;}
32
+ .pen h5, .pen h6{font-size:1.2em;}
33
+
34
+ /* list */
35
+ .pen ul, .pen ol{margin-left:1.2em;}
36
+ .pen ul, .pen-ul{list-style:disc;}
37
+ .pen ol, .pen-ol{list-style:decimal;}
38
+ .pen li ul, .pen li ol, .pen-ul ul, .pen-ul ol, .pen-ol ul, .pen-ol ol{margin:0 2em 0 1.2em;}
39
+ .pen li ul, .pen-ul ul, .pen-ol ul{list-style: circle;}
40
+
41
+ /* pen menu */
42
+ .pen-menu, .pen-input{font-size:14px;line-height:1;}
43
+ .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;}
44
+ .pen-menu:after {top:100%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none;}
45
+ .pen-menu:after {border-color:rgba(51, 51, 51, 0);border-top-color:#333;border-width:6px;left:50%;margin-left:-6px;}
46
+ .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;}
47
+ .pen-icon:first-of-type{border-top-left-radius:3px;border-bottom-left-radius:3px;}
48
+ .pen-icon:last-of-type{border-top-right-radius:3px;border-bottom-right-radius:3px;}
49
+ .pen-icon:hover{background:#000;}
50
+ .pen-icon.active{color:#1abf89;background:#000;box-shadow:inset 2px 2px 4px #000;}
51
+ .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;}
52
+ .pen-input:focus{outline:none;}
53
+
54
+ .pen-textarea{display:block;background:#f8f8f8;padding:20px;}
55
+ .pen textarea{font-size:14px;border:none;background:none;width:100%;_height:200px;min-height:200px;resize:none;}
56
+
57
+ @font-face {
58
+ font-family: 'pen';
59
+ src: url('<%= font_path("fontello.eol") %>');
60
+ src: url('<%= font_path("fontello.eot") %>') format('embedded-opentype'),
61
+ url('<%= font_path("fontello.woff") %>') format('woff'),
62
+ url('<%= font_path("fontello.ttf") %>') format('truetype'),
63
+ url('<%= font_path("fontello.svg") %>') format('svg');
64
+ font-weight: normal;
65
+ font-style: normal;
66
+ }
67
+
68
+ .pen-menu [class^="icon-"]:before, .pen-menu [class*=" icon-"]:before {
69
+ font-family: "pen";
70
+ font-style: normal;
71
+ font-weight: normal;
72
+ speak: none;
73
+ display: inline-block;
74
+ text-decoration: inherit;
75
+ width: 1em;
76
+ margin-right: .2em;
77
+ text-align: center;
78
+ font-variant: normal;
79
+ text-transform: none;
80
+ line-height: 1em;
81
+ margin-left: .2em;
82
+ }
83
+
84
+ .pen-menu .icon-location:before { content: '\e815'; } /* '' */
85
+ .pen-menu .icon-fit:before { content: '\e80f'; } /* '' */
86
+ .pen-menu .icon-bold:before { content: '\e805'; } /* '' */
87
+ .pen-menu .icon-italic:before { content: '\e806'; } /* '' */
88
+ .pen-menu .icon-justifyleft:before { content: '\e80a'; } /* '' */
89
+ .pen-menu .icon-justifycenter:before { content: '\e80b'; } /* '' */
90
+ .pen-menu .icon-justifyright:before { content: '\e80c'; } /* '' */
91
+ .pen-menu .icon-justifyfull:before { content: '\e80d'; } /* '' */
92
+ .pen-menu .icon-outdent:before { content: '\e800'; } /* '' */
93
+ .pen-menu .icon-indent:before { content: '\e801'; } /* '' */
94
+ .pen-menu .icon-mode:before { content: '\e813'; } /* '' */
95
+ .pen-menu .icon-fullscreen:before { content: '\e80e'; } /* '' */
96
+ .pen-menu .icon-insertunorderedlist:before { content: '\e802'; } /* '' */
97
+ .pen-menu .icon-insertorderedlist:before { content: '\e803'; } /* '' */
98
+ .pen-menu .icon-strikethrough:before { content: '\e807'; } /* '' */
99
+ .pen-menu .icon-underline:before { content: '\e804'; } /* '' */
100
+ .pen-menu .icon-blockquote:before { content: '\e814'; } /* '' */
101
+ .pen-menu .icon-undo:before { content: '\e817'; } /* '' */
102
+ .pen-menu .icon-pre:before { content: '\e816'; } /* '' */
103
+ .pen-menu .icon-unlink:before { content: '\e811'; } /* '' */
104
+ .pen-menu .icon-superscript:before { content: '\e808'; } /* '' */
105
+ .pen-menu .icon-subscript:before { content: '\e809'; } /* '' */
106
+ .pen-menu .icon-inserthorizontalrule:before { content: '\e818'; } /* '' */
107
+ .pen-menu .icon-pin:before { content: '\e812'; } /* '' */
108
+ .pen-menu .icon-createlink:before { content: '\e810'; } /* '' */
109
+
110
+ .pen {
111
+ position: relative;
112
+ }
113
+ .pen.hinted h1:before,
114
+ .pen.hinted h2:before,
115
+ .pen.hinted h3:before,
116
+ .pen.hinted h4:before,
117
+ .pen.hinted h5:before,
118
+ .pen.hinted h6:before,
119
+ .pen.hinted blockquote:before,
120
+ .pen.hinted hr:before {
121
+ color: #eee;
122
+ position: absolute;
123
+ right: 100%;
124
+ white-space: nowrap;
125
+ padding-right: 10px;
126
+ }
127
+ .pen.hinted blockquote { border-left: 0; margin-left: 0; padding-left: 0; }
128
+ .pen.hinted blockquote:before {
129
+ color: #1abf89;
130
+ content: ">";
131
+ font-weight: bold;
132
+ vertical-align: center;
133
+ }
134
+ .pen.hinted h1:before { content: "#";}
135
+ .pen.hinted h2:before { content: "##";}
136
+ .pen.hinted h3:before { content: "###";}
137
+ .pen.hinted h4:before { content: "####";}
138
+ .pen.hinted h5:before { content: "#####";}
139
+ .pen.hinted h6:before { content: "######";}
140
+ .pen.hinted hr:before { content: "﹘﹘﹘"; line-height: 1.2; vertical-align: bottom; }
141
+
142
+ .pen.hinted pre:before, .pen.hinted pre:after {
143
+ content: "```";
144
+ display: block;
145
+ color: #ccc;
146
+ }
147
+
148
+ .pen.hinted ul { list-style: none; }
149
+ .pen.hinted ul li:before {
150
+ content: "*";
151
+ color: #999;
152
+ line-height: 1;
153
+ vertical-align: bottom;
154
+ margin-left: -1.2em;
155
+ display: inline-block;
156
+ width: 1.2em;
157
+ }
158
+
159
+ .pen.hinted b:before, .pen.hinted b:after { content: "**"; color: #eee; font-weight: normal; }
160
+ .pen.hinted i:before, .pen.hinted i:after { content: "*"; color: #eee; }
161
+
162
+ .pen.hinted a { text-decoration: none; }
163
+ .pen.hinted a:before {content: "["; color: #ddd; }
164
+ .pen.hinted a:after { content: "](" attr(href) ")"; color: #ddd; }
@@ -1,5 +1,5 @@
1
1
  module Pen
2
2
  module Rails
3
- VERSION = "0.0.3"
3
+ VERSION = "0.0.4"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pen-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lin Xiangyu
@@ -19,6 +19,14 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - MIT-LICENSE
21
21
  - README.md
22
+ - app/assets/fonts/fontello.eot
23
+ - app/assets/fonts/fontello.svg
24
+ - app/assets/fonts/fontello.ttf
25
+ - app/assets/fonts/fontello.woff
26
+ - app/assets/javascripts/backbone-validation.js
27
+ - app/assets/javascripts/markdown.js
28
+ - app/assets/javascripts/pen.js
29
+ - app/assets/stylesheets/pen.css
22
30
  - lib/pen-rails.rb
23
31
  - lib/pen-rails/version.rb
24
32
  homepage: http://www.github.com/oa414/pen-rails