Version bump

This commit is contained in:
Neil Lalonde 2015-12-18 11:09:25 -05:00
commit 0a4ae61b2c
3248 changed files with 8613 additions and 4471 deletions

View File

@ -7,6 +7,7 @@ app/assets/javascripts/vendor.js
app/assets/javascripts/locales/i18n.js
app/assets/javascripts/defer/html-sanitizer-bundle.js
app/assets/javascripts/ember-addons/
app/assets/javascripts/admin/lib/autosize.js.es6
lib/javascripts/locale/
lib/javascripts/messageformat.js
lib/javascripts/moment.js
@ -16,8 +17,8 @@ lib/es6_module_transpiler/support/es6-module-transpiler.js
public/javascripts/
spec/phantom_js/smoke_test.js
vendor/
test/javascripts/helpers/
test/javascripts/test_helper.js
test/javascripts/test_helper.js
test/javascripts/fixtures
test/javascripts/helpers/assertions.js
app/assets/javascripts/ember-addons/

1
.gitignore vendored
View File

@ -47,6 +47,7 @@ log/
!/plugins/emoji/
!/plugins/lazyYT/
!/plugins/poll/
!/plugins/discourse-details/
/plugins/*/auto_generated/
/spec/fixtures/plugins/my_plugin/auto_generated

View File

@ -47,7 +47,7 @@ gem 'aws-sdk', require: false
gem 'excon', require: false
gem 'unf', require: false
gem 'email_reply_parser'
gem 'discourse_email_parser'
# note: for image_optim to correctly work you need to follow
# https://github.com/toy/image_optim

View File

@ -113,9 +113,9 @@ GEM
diff-lcs (1.2.5)
discourse-qunit-rails (0.0.8)
railties
discourse_email_parser (0.6.1)
docile (1.1.5)
dotenv (2.0.2)
email_reply_parser (0.5.8)
ember-data-source (1.0.0.beta.16.1)
ember-source (~> 1.8)
ember-handlebars-template (0.1.5)
@ -186,14 +186,14 @@ GEM
thor (~> 0.15)
libv8 (3.16.14.11)
listen (0.7.3)
logster (1.0.0.3.pre)
logster (1.0.1)
loofah (2.0.3)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
mail (2.6.3)
mime-types (>= 1.16, < 3)
memory_profiler (0.9.4)
message_bus (1.0.16)
message_bus (1.1.1)
rack (>= 1.1.3)
redis
metaclass (0.0.4)
@ -247,7 +247,7 @@ GEM
omniauth-twitter (1.2.1)
json (~> 1.3)
omniauth-oauth (~> 1.1)
onebox (1.5.28)
onebox (1.5.29)
moneta (~> 0.8)
multi_json (~> 1.11)
mustache
@ -315,7 +315,7 @@ GEM
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
trollop (>= 1.16.2)
redis (3.2.1)
redis (3.2.2)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
ref (2.0.0)
@ -451,7 +451,7 @@ DEPENDENCIES
byebug
certified
discourse-qunit-rails
email_reply_parser
discourse_email_parser
ember-rails
ember-source (= 1.12.1)
excon

View File

@ -69,7 +69,9 @@ Before contributing to Discourse:
1. Please read the complete mission statements on [**discourse.org**](http://www.discourse.org). Yes we actually believe this stuff; you should too.
2. Read and sign the [**Electronic Discourse Forums Contribution License Agreement**](http://discourse.org/cla).
3. Dig into [**CONTRIBUTING.MD**](CONTRIBUTING.md), which covers submitting bugs, requesting new features, preparing your code for a pull request, etc.
4. Not sure what to work on? [**We've got some ideas.**](http://meta.discourse.org/t/so-you-want-to-help-out-with-discourse/3823)
4. Always strive to collaborate [with mutual respect](https://github.com/discourse/discourse/blob/master/docs/code-of-conduct.md).
5. Not sure what to work on? [**We've got some ideas.**](http://meta.discourse.org/t/so-you-want-to-help-out-with-discourse/3823)
We look forward to seeing your pull requests!

Binary file not shown.

View File

@ -1,6 +1,6 @@
<?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" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="fontawesomeregular" horiz-adv-x="1536" >
@ -219,8 +219,8 @@
<glyph unicode="&#xf0d1;" horiz-adv-x="1792" d="M640 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM256 640h384v256h-158q-13 0 -22 -9l-195 -195q-9 -9 -9 -22v-30zM1536 128q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1792 1216v-1024q0 -15 -4 -26.5t-13.5 -18.5 t-16.5 -11.5t-23.5 -6t-22.5 -2t-25.5 0t-22.5 0.5q0 -106 -75 -181t-181 -75t-181 75t-75 181h-384q0 -106 -75 -181t-181 -75t-181 75t-75 181h-64q-3 0 -22.5 -0.5t-25.5 0t-22.5 2t-23.5 6t-16.5 11.5t-13.5 18.5t-4 26.5q0 26 19 45t45 19v320q0 8 -0.5 35t0 38 t2.5 34.5t6.5 37t14 30.5t22.5 30l198 198q19 19 50.5 32t58.5 13h160v192q0 26 19 45t45 19h1024q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf0d2;" d="M1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103q-111 0 -218 32q59 93 78 164q9 34 54 211q20 -39 73 -67.5t114 -28.5q121 0 216 68.5t147 188.5t52 270q0 114 -59.5 214t-172.5 163t-255 63q-105 0 -196 -29t-154.5 -77t-109 -110.5t-67 -129.5t-21.5 -134 q0 -104 40 -183t117 -111q30 -12 38 20q2 7 8 31t8 30q6 23 -11 43q-51 61 -51 151q0 151 104.5 259.5t273.5 108.5q151 0 235.5 -82t84.5 -213q0 -170 -68.5 -289t-175.5 -119q-61 0 -98 43.5t-23 104.5q8 35 26.5 93.5t30 103t11.5 75.5q0 50 -27 83t-77 33 q-62 0 -105 -57t-43 -142q0 -73 25 -122l-99 -418q-17 -70 -13 -177q-206 91 -333 281t-127 423q0 209 103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf0d3;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-725q85 122 108 210q9 34 53 209q21 -39 73.5 -67t112.5 -28q181 0 295.5 147.5t114.5 373.5q0 84 -35 162.5t-96.5 139t-152.5 97t-197 36.5q-104 0 -194.5 -28.5t-153 -76.5 t-107.5 -109.5t-66.5 -128t-21.5 -132.5q0 -102 39.5 -180t116.5 -110q13 -5 23.5 0t14.5 19q10 44 15 61q6 23 -11 42q-50 62 -50 150q0 150 103.5 256.5t270.5 106.5q149 0 232.5 -81t83.5 -210q0 -168 -67.5 -286t-173.5 -118q-60 0 -97 43.5t-23 103.5q8 34 26.5 92.5 t29.5 102t11 74.5q0 49 -26.5 81.5t-75.5 32.5q-61 0 -103.5 -56.5t-42.5 -139.5q0 -72 24 -121l-98 -414q-24 -100 -7 -254h-183q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960z" />
<glyph unicode="&#xf0d4;" d="M829 318q0 -76 -58.5 -112.5t-139.5 -36.5q-41 0 -80.5 9.5t-75.5 28.5t-58 53t-22 78q0 46 25 80t65.5 51.5t82 25t84.5 7.5q20 0 31 -2q2 -1 23 -16.5t26 -19t23 -18t24.5 -22t19 -22.5t17 -26t9 -26.5t4.5 -31.5zM755 863q0 -60 -33 -99.5t-92 -39.5q-53 0 -93 42.5 t-57.5 96.5t-17.5 106q0 61 32 104t92 43q53 0 93.5 -45t58 -101t17.5 -107zM861 1120l88 64h-265q-85 0 -161 -32t-127.5 -98t-51.5 -153q0 -93 64.5 -154.5t158.5 -61.5q22 0 43 3q-13 -29 -13 -54q0 -44 40 -94q-175 -12 -257 -63q-47 -29 -75.5 -73t-28.5 -95 q0 -43 18.5 -77.5t48.5 -56.5t69 -37t77.5 -21t76.5 -6q60 0 120.5 15.5t113.5 46t86 82.5t33 117q0 49 -20 89.5t-49 66.5t-58 47.5t-49 44t-20 44.5t15.5 42.5t37.5 39.5t44 42t37.5 59.5t15.5 82.5q0 60 -22.5 99.5t-72.5 90.5h83zM1152 672h128v64h-128v128h-64v-128 h-128v-64h128v-160h64v160zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf0d5;" horiz-adv-x="1664" d="M735 740q0 -36 32 -70.5t77.5 -68t90.5 -73.5t77 -104t32 -142q0 -90 -48 -173q-72 -122 -211 -179.5t-298 -57.5q-132 0 -246.5 41.5t-171.5 137.5q-37 60 -37 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 42 -47.5 74t-15.5 73q0 36 21 85q-46 -4 -68 -4 q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q77 66 182.5 98t217.5 32h418l-138 -88h-131q74 -63 112 -133t38 -160q0 -72 -24.5 -129.5t-59 -93t-69.5 -65t-59.5 -61.5t-24.5 -66zM589 836q38 0 78 16.5t66 43.5q53 57 53 159q0 58 -17 125t-48.5 129.5 t-84.5 103.5t-117 41q-42 0 -82.5 -19.5t-65.5 -52.5q-47 -59 -47 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26zM591 -37q58 0 111.5 13t99 39t73 73t27.5 109q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -48 2 q-53 0 -105 -7t-107.5 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -70 35 -123.5t91.5 -83t119 -44t127.5 -14.5zM1401 839h213v-108h-213v-219h-105v219h-212v108h212v217h105v-217z" />
<glyph unicode="&#xf0d4;" d="M917 631q0 26 -6 64h-362v-132h217q-3 -24 -16.5 -50t-37.5 -53t-66.5 -44.5t-96.5 -17.5q-99 0 -169 71t-70 171t70 171t169 71q92 0 153 -59l104 101q-108 100 -257 100q-160 0 -272 -112.5t-112 -271.5t112 -271.5t272 -112.5q165 0 266.5 105t101.5 270zM1262 585 h109v110h-109v110h-110v-110h-110v-110h110v-110h110v110zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf0d5;" horiz-adv-x="2304" d="M1437 623q0 -208 -87 -370.5t-248 -254t-369 -91.5q-149 0 -285 58t-234 156t-156 234t-58 285t58 285t156 234t234 156t285 58q286 0 491 -192l-199 -191q-117 113 -292 113q-123 0 -227.5 -62t-165.5 -168.5t-61 -232.5t61 -232.5t165.5 -168.5t227.5 -62 q83 0 152.5 23t114.5 57.5t78.5 78.5t49 83t21.5 74h-416v252h692q12 -63 12 -122zM2304 745v-210h-209v-209h-210v209h-209v210h209v209h210v-209h209z" />
<glyph unicode="&#xf0d6;" horiz-adv-x="1920" d="M768 384h384v96h-128v448h-114l-148 -137l77 -80q42 37 55 57h2v-288h-128v-96zM1280 640q0 -70 -21 -142t-59.5 -134t-101.5 -101t-138 -39t-138 39t-101.5 101t-59.5 134t-21 142t21 142t59.5 134t101.5 101t138 39t138 -39t101.5 -101t59.5 -134t21 -142zM1792 384 v512q-106 0 -181 75t-75 181h-1152q0 -106 -75 -181t-181 -75v-512q106 0 181 -75t75 -181h1152q0 106 75 181t181 75zM1920 1216v-1152q0 -26 -19 -45t-45 -19h-1792q-26 0 -45 19t-19 45v1152q0 26 19 45t45 19h1792q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf0d7;" horiz-adv-x="1024" d="M1024 832q0 -26 -19 -45l-448 -448q-19 -19 -45 -19t-45 19l-448 448q-19 19 -19 45t19 45t45 19h896q26 0 45 -19t19 -45z" />
<glyph unicode="&#xf0d8;" horiz-adv-x="1024" d="M1024 320q0 -26 -19 -45t-45 -19h-896q-26 0 -45 19t-19 45t19 45l448 448q19 19 45 19t45 -19l448 -448q19 -19 19 -45z" />
@ -362,7 +362,7 @@
<glyph unicode="&#xf169;" d="M685 771q0 1 -126 222q-21 34 -52 34h-184q-18 0 -26 -11q-7 -12 1 -29l125 -216v-1l-196 -346q-9 -14 0 -28q8 -13 24 -13h185q31 0 50 36zM1309 1268q-7 12 -24 12h-187q-30 0 -49 -35l-411 -729q1 -2 262 -481q20 -35 52 -35h184q18 0 25 12q8 13 -1 28l-260 476v1 l409 723q8 16 0 28zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf16a;" horiz-adv-x="1792" d="M1280 640q0 37 -30 54l-512 320q-31 20 -65 2q-33 -18 -33 -56v-640q0 -38 33 -56q16 -8 31 -8q20 0 34 10l512 320q30 17 30 54zM1792 640q0 -96 -1 -150t-8.5 -136.5t-22.5 -147.5q-16 -73 -69 -123t-124 -58q-222 -25 -671 -25t-671 25q-71 8 -124.5 58t-69.5 123 q-14 65 -21.5 147.5t-8.5 136.5t-1 150t1 150t8.5 136.5t22.5 147.5q16 73 69 123t124 58q222 25 671 25t671 -25q71 -8 124.5 -58t69.5 -123q14 -65 21.5 -147.5t8.5 -136.5t1 -150z" />
<glyph unicode="&#xf16b;" horiz-adv-x="1792" d="M402 829l494 -305l-342 -285l-490 319zM1388 274v-108l-490 -293v-1l-1 1l-1 -1v1l-489 293v108l147 -96l342 284v2l1 -1l1 1v-2l343 -284zM554 1418l342 -285l-494 -304l-338 270zM1390 829l338 -271l-489 -319l-343 285zM1239 1418l489 -319l-338 -270l-494 304z" />
<glyph unicode="&#xf16c;" horiz-adv-x="1408" d="M928 135v-151l-707 -1v151zM1169 481v-701l-1 -35v-1h-1132l-35 1h-1v736h121v-618h928v618h120zM241 393l704 -65l-13 -150l-705 65zM309 709l683 -183l-39 -146l-683 183zM472 1058l609 -360l-77 -130l-609 360zM832 1389l398 -585l-124 -85l-399 584zM1285 1536 l121 -697l-149 -26l-121 697z" />
<glyph unicode="&#xf16c;" d="M1289 -96h-1118v480h-160v-640h1438v640h-160v-480zM347 428l33 157l783 -165l-33 -156zM450 802l67 146l725 -339l-67 -145zM651 1158l102 123l614 -513l-102 -123zM1048 1536l477 -641l-128 -96l-477 641zM330 65v159h800v-159h-800z" />
<glyph unicode="&#xf16d;" d="M1362 110v648h-135q20 -63 20 -131q0 -126 -64 -232.5t-174 -168.5t-240 -62q-197 0 -337 135.5t-140 327.5q0 68 20 131h-141v-648q0 -26 17.5 -43.5t43.5 -17.5h1069q25 0 43 17.5t18 43.5zM1078 643q0 124 -90.5 211.5t-218.5 87.5q-127 0 -217.5 -87.5t-90.5 -211.5 t90.5 -211.5t217.5 -87.5q128 0 218.5 87.5t90.5 211.5zM1362 1003v165q0 28 -20 48.5t-49 20.5h-174q-29 0 -49 -20.5t-20 -48.5v-165q0 -29 20 -49t49 -20h174q29 0 49 20t20 49zM1536 1211v-1142q0 -81 -58 -139t-139 -58h-1142q-81 0 -139 58t-58 139v1142q0 81 58 139 t139 58h1142q81 0 139 -58t58 -139z" />
<glyph unicode="&#xf16e;" d="M1248 1408q119 0 203.5 -84.5t84.5 -203.5v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960zM698 640q0 88 -62 150t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150zM1262 640q0 88 -62 150 t-150 62t-150 -62t-62 -150t62 -150t150 -62t150 62t62 150z" />
<glyph unicode="&#xf170;" d="M768 914l201 -306h-402zM1133 384h94l-459 691l-459 -691h94l104 160h522zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
@ -410,7 +410,7 @@
<glyph unicode="&#xf19c;" horiz-adv-x="2048" d="M960 1536l960 -384v-128h-128q0 -26 -20.5 -45t-48.5 -19h-1526q-28 0 -48.5 19t-20.5 45h-128v128zM256 896h256v-768h128v768h256v-768h128v768h256v-768h128v768h256v-768h59q28 0 48.5 -19t20.5 -45v-64h-1664v64q0 26 20.5 45t48.5 19h59v768zM1851 -64 q28 0 48.5 -19t20.5 -45v-128h-1920v128q0 26 20.5 45t48.5 19h1782z" />
<glyph unicode="&#xf19d;" horiz-adv-x="2304" d="M1774 700l18 -316q4 -69 -82 -128t-235 -93.5t-323 -34.5t-323 34.5t-235 93.5t-82 128l18 316l574 -181q22 -7 48 -7t48 7zM2304 1024q0 -23 -22 -31l-1120 -352q-4 -1 -10 -1t-10 1l-652 206q-43 -34 -71 -111.5t-34 -178.5q63 -36 63 -109q0 -69 -58 -107l58 -433 q2 -14 -8 -25q-9 -11 -24 -11h-192q-15 0 -24 11q-10 11 -8 25l58 433q-58 38 -58 107q0 73 65 111q11 207 98 330l-333 104q-22 8 -22 31t22 31l1120 352q4 1 10 1t10 -1l1120 -352q22 -8 22 -31z" />
<glyph unicode="&#xf19e;" d="M859 579l13 -707q-62 11 -105 11q-41 0 -105 -11l13 707q-40 69 -168.5 295.5t-216.5 374.5t-181 287q58 -15 108 -15q43 0 111 15q63 -111 133.5 -229.5t167 -276.5t138.5 -227q37 61 109.5 177.5t117.5 190t105 176t107 189.5q54 -14 107 -14q56 0 114 14v0 q-28 -39 -60 -88.5t-49.5 -78.5t-56.5 -96t-49 -84q-146 -248 -353 -610z" />
<glyph unicode="&#xf1a0;" horiz-adv-x="1280" d="M981 197q0 25 -7 49t-14.5 42t-27 41.5t-29.5 35t-38.5 34.5t-36.5 29t-41.5 30t-36.5 26q-16 2 -49 2q-53 0 -104.5 -7t-107 -25t-97 -46t-68.5 -74.5t-27 -105.5q0 -56 23.5 -102t61 -75.5t87 -50t100 -29t101.5 -8.5q58 0 111.5 13t99 39t73 73t27.5 109zM864 1055 q0 59 -17 125.5t-48 129t-84 103.5t-117 41q-42 0 -82.5 -19.5t-66.5 -52.5q-46 -59 -46 -160q0 -46 10 -97.5t31.5 -103t52 -92.5t75 -67t96.5 -26q37 0 77.5 16.5t65.5 43.5q53 56 53 159zM752 1536h417l-137 -88h-132q75 -63 113 -133t38 -160q0 -72 -24.5 -129.5 t-59.5 -93t-69.5 -65t-59 -61.5t-24.5 -66q0 -36 32 -70.5t77 -68t90.5 -73.5t77.5 -104t32 -142q0 -91 -49 -173q-71 -122 -209.5 -179.5t-298.5 -57.5q-132 0 -246.5 41.5t-172.5 137.5q-36 59 -36 131q0 81 44.5 150t118.5 115q131 82 404 100q-32 41 -47.5 73.5 t-15.5 73.5q0 40 21 85q-46 -4 -68 -4q-148 0 -249.5 96.5t-101.5 244.5q0 82 36 159t99 131q76 66 182 98t218 32z" />
<glyph unicode="&#xf1a0;" d="M768 750h725q12 -67 12 -128q0 -217 -91 -387.5t-259.5 -266.5t-386.5 -96q-157 0 -299 60.5t-245 163.5t-163.5 245t-60.5 299t60.5 299t163.5 245t245 163.5t299 60.5q300 0 515 -201l-209 -201q-123 119 -306 119q-129 0 -238.5 -65t-173.5 -176.5t-64 -243.5 t64 -243.5t173.5 -176.5t238.5 -65q87 0 160 24t120 60t82 82t51.5 87t22.5 78h-436v264z" />
<glyph unicode="&#xf1a1;" horiz-adv-x="1792" d="M1095 369q16 -16 0 -31q-62 -62 -199 -62t-199 62q-16 15 0 31q6 6 15 6t15 -6q48 -49 169 -49q120 0 169 49q6 6 15 6t15 -6zM788 550q0 -37 -26 -63t-63 -26t-63.5 26t-26.5 63q0 38 26.5 64t63.5 26t63 -26.5t26 -63.5zM1183 550q0 -37 -26.5 -63t-63.5 -26t-63 26 t-26 63t26 63.5t63 26.5t63.5 -26t26.5 -64zM1434 670q0 49 -35 84t-85 35t-86 -36q-130 90 -311 96l63 283l200 -45q0 -37 26 -63t63 -26t63.5 26.5t26.5 63.5t-26.5 63.5t-63.5 26.5q-54 0 -80 -50l-221 49q-19 5 -25 -16l-69 -312q-180 -7 -309 -97q-35 37 -87 37 q-50 0 -85 -35t-35 -84q0 -35 18.5 -64t49.5 -44q-6 -27 -6 -56q0 -142 140 -243t337 -101q198 0 338 101t140 243q0 32 -7 57q30 15 48 43.5t18 63.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191 t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf1a2;" d="M939 407q13 -13 0 -26q-53 -53 -171 -53t-171 53q-13 13 0 26q5 6 13 6t13 -6q42 -42 145 -42t145 42q5 6 13 6t13 -6zM676 563q0 -31 -23 -54t-54 -23t-54 23t-23 54q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1014 563q0 -31 -23 -54t-54 -23t-54 23t-23 54 q0 32 22.5 54.5t54.5 22.5t54.5 -22.5t22.5 -54.5zM1229 666q0 42 -30 72t-73 30q-42 0 -73 -31q-113 78 -267 82l54 243l171 -39q1 -32 23.5 -54t53.5 -22q32 0 54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5q-48 0 -69 -43l-189 42q-17 5 -21 -13l-60 -268q-154 -6 -265 -83 q-30 32 -74 32q-43 0 -73 -30t-30 -72q0 -30 16 -55t42 -38q-5 -25 -5 -48q0 -122 120 -208.5t289 -86.5q170 0 290 86.5t120 208.5q0 25 -6 49q25 13 40.5 37.5t15.5 54.5zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960 q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
<glyph unicode="&#xf1a3;" d="M866 697l90 27v62q0 79 -58 135t-138 56t-138 -55.5t-58 -134.5v-283q0 -20 -14 -33.5t-33 -13.5t-32.5 13.5t-13.5 33.5v120h-151v-122q0 -82 57.5 -139t139.5 -57q81 0 138.5 56.5t57.5 136.5v280q0 19 13.5 33t33.5 14q19 0 32.5 -14t13.5 -33v-54zM1199 502v122h-150 v-126q0 -20 -13.5 -33.5t-33.5 -13.5q-19 0 -32.5 14t-13.5 33v123l-90 -26l-60 28v-123q0 -80 58 -137t139 -57t138.5 57t57.5 139zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103 t385.5 -103t279.5 -279.5t103 -385.5z" />
@ -454,7 +454,7 @@
<glyph unicode="&#xf1cb;" horiz-adv-x="1792" d="M216 367l603 -402v359l-334 223zM154 511l193 129l-193 129v-258zM973 -35l603 402l-269 180l-334 -223v-359zM896 458l272 182l-272 182l-272 -182zM485 733l334 223v359l-603 -402zM1445 640l193 -129v258zM1307 733l269 180l-603 402v-359zM1792 913v-546 q0 -41 -34 -64l-819 -546q-21 -13 -43 -13t-43 13l-819 546q-34 23 -34 64v546q0 41 34 64l819 546q21 13 43 13t43 -13l819 -546q34 -23 34 -64z" />
<glyph unicode="&#xf1cc;" horiz-adv-x="2048" d="M1800 764q111 -46 179.5 -145.5t68.5 -221.5q0 -164 -118 -280.5t-285 -116.5q-4 0 -11.5 0.5t-10.5 0.5h-1209h-1h-2h-5q-170 10 -288 125.5t-118 280.5q0 110 55 203t147 147q-12 39 -12 82q0 115 82 196t199 81q95 0 172 -58q75 154 222.5 248t326.5 94 q166 0 306 -80.5t221.5 -218.5t81.5 -301q0 -6 -0.5 -18t-0.5 -18zM468 498q0 -122 84 -193t208 -71q137 0 240 99q-16 20 -47.5 56.5t-43.5 50.5q-67 -65 -144 -65q-55 0 -93.5 33.5t-38.5 87.5q0 53 38.5 87t91.5 34q44 0 84.5 -21t73 -55t65 -75t69 -82t77 -75t97 -55 t121.5 -21q121 0 204.5 71.5t83.5 190.5q0 121 -84 192t-207 71q-143 0 -241 -97q14 -16 29.5 -34t34.5 -40t29 -34q66 64 142 64q52 0 92 -33t40 -84q0 -57 -37 -91.5t-94 -34.5q-43 0 -82.5 21t-72 55t-65.5 75t-69.5 82t-77.5 75t-96.5 55t-118.5 21q-122 0 -207 -70.5 t-85 -189.5z" />
<glyph unicode="&#xf1cd;" horiz-adv-x="1792" d="M896 1536q182 0 348 -71t286 -191t191 -286t71 -348t-71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71zM896 1408q-190 0 -361 -90l194 -194q82 28 167 28t167 -28l194 194q-171 90 -361 90zM218 279l194 194 q-28 82 -28 167t28 167l-194 194q-90 -171 -90 -361t90 -361zM896 -128q190 0 361 90l-194 194q-82 -28 -167 -28t-167 28l-194 -194q171 -90 361 -90zM896 256q159 0 271.5 112.5t112.5 271.5t-112.5 271.5t-271.5 112.5t-271.5 -112.5t-112.5 -271.5t112.5 -271.5 t271.5 -112.5zM1380 473l194 -194q90 171 90 361t-90 361l-194 -194q28 -82 28 -167t-28 -167z" />
<glyph unicode="&#xf1ce;" horiz-adv-x="1792" d="M1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348q0 222 101 414.5t276.5 317t390.5 155.5v-260q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5t136.5 204t51 248.5 q0 230 -145.5 406t-366.5 221v260q215 -31 390.5 -155.5t276.5 -317t101 -414.5z" />
<glyph unicode="&#xf1ce;" horiz-adv-x="1792" d="M1760 640q0 -176 -68.5 -336t-184 -275.5t-275.5 -184t-336 -68.5t-336 68.5t-275.5 184t-184 275.5t-68.5 336q0 213 97 398.5t265 305.5t374 151v-228q-221 -45 -366.5 -221t-145.5 -406q0 -130 51 -248.5t136.5 -204t204 -136.5t248.5 -51t248.5 51t204 136.5 t136.5 204t51 248.5q0 230 -145.5 406t-366.5 221v228q206 -31 374 -151t265 -305.5t97 -398.5z" />
<glyph unicode="&#xf1d0;" horiz-adv-x="1792" d="M19 662q8 217 116 406t305 318h5q0 -1 -1 -3q-8 -8 -28 -33.5t-52 -76.5t-60 -110.5t-44.5 -135.5t-14 -150.5t39 -157.5t108.5 -154q50 -50 102 -69.5t90.5 -11.5t69.5 23.5t47 32.5l16 16q39 51 53 116.5t6.5 122.5t-21 107t-26.5 80l-14 29q-10 25 -30.5 49.5t-43 41 t-43.5 29.5t-35 19l-13 6l104 115q39 -17 78 -52t59 -61l19 -27q1 48 -18.5 103.5t-40.5 87.5l-20 31l161 183l160 -181q-33 -46 -52.5 -102.5t-22.5 -90.5l-4 -33q22 37 61.5 72.5t67.5 52.5l28 17l103 -115q-44 -14 -85 -50t-60 -65l-19 -29q-31 -56 -48 -133.5t-7 -170 t57 -156.5q33 -45 77.5 -60.5t85 -5.5t76 26.5t57.5 33.5l21 16q60 53 96.5 115t48.5 121.5t10 121.5t-18 118t-37 107.5t-45.5 93t-45 72t-34.5 47.5l-13 17q-14 13 -7 13l10 -3q40 -29 62.5 -46t62 -50t64 -58t58.5 -65t55.5 -77t45.5 -88t38 -103t23.5 -117t10.5 -136 q3 -259 -108 -465t-312 -321t-456 -115q-185 0 -351 74t-283.5 198t-184 293t-60.5 353z" />
<glyph unicode="&#xf1d1;" horiz-adv-x="1792" d="M874 -102v-66q-208 6 -385 109.5t-283 275.5l58 34q29 -49 73 -99l65 57q148 -168 368 -212l-17 -86q65 -12 121 -13zM276 428l-83 -28q22 -60 49 -112l-57 -33q-98 180 -98 385t98 385l57 -33q-30 -56 -49 -112l82 -28q-35 -100 -35 -212q0 -109 36 -212zM1528 251 l58 -34q-106 -172 -283 -275.5t-385 -109.5v66q56 1 121 13l-17 86q220 44 368 212l65 -57q44 50 73 99zM1377 805l-233 -80q14 -42 14 -85t-14 -85l232 -80q-31 -92 -98 -169l-185 162q-57 -67 -147 -85l48 -241q-52 -10 -98 -10t-98 10l48 241q-90 18 -147 85l-185 -162 q-67 77 -98 169l232 80q-14 42 -14 85t14 85l-233 80q33 93 99 169l185 -162q59 68 147 86l-48 240q44 10 98 10t98 -10l-48 -240q88 -18 147 -86l185 162q66 -76 99 -169zM874 1448v-66q-65 -2 -121 -13l17 -86q-220 -42 -368 -211l-65 56q-38 -42 -73 -98l-57 33 q106 172 282 275.5t385 109.5zM1705 640q0 -205 -98 -385l-57 33q27 52 49 112l-83 28q36 103 36 212q0 112 -35 212l82 28q-19 56 -49 112l57 33q98 -180 98 -385zM1585 1063l-57 -33q-35 56 -73 98l-65 -56q-148 169 -368 211l17 86q-56 11 -121 13v66q209 -6 385 -109.5 t282 -275.5zM1748 640q0 173 -67.5 331t-181.5 272t-272 181.5t-331 67.5t-331 -67.5t-272 -181.5t-181.5 -272t-67.5 -331t67.5 -331t181.5 -272t272 -181.5t331 -67.5t331 67.5t272 181.5t181.5 272t67.5 331zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71 t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf1d2;" d="M582 228q0 -66 -93 -66q-107 0 -107 63q0 64 98 64q102 0 102 -61zM546 694q0 -85 -74 -85q-77 0 -77 84q0 90 77 90q36 0 55 -25.5t19 -63.5zM712 769v125q-78 -29 -135 -29q-50 29 -110 29q-86 0 -145 -57t-59 -143q0 -50 29.5 -102t73.5 -67v-3q-38 -17 -38 -85 q0 -53 41 -77v-3q-113 -37 -113 -139q0 -45 20 -78.5t54 -51t72 -25.5t81 -8q224 0 224 188q0 67 -48 99t-126 46q-27 5 -51.5 20.5t-24.5 39.5q0 44 49 52q77 15 122 70t45 134q0 24 -10 52q37 9 49 13zM771 350h137q-2 27 -2 82v387q0 46 2 69h-137q3 -23 3 -71v-392 q0 -50 -3 -75zM1280 366v121q-30 -21 -68 -21q-53 0 -53 82v225h52q9 0 26.5 -1t26.5 -1v117h-105q0 82 3 102h-140q4 -24 4 -55v-47h-60v-117q36 3 37 3q3 0 11 -0.5t12 -0.5v-2h-2v-217q0 -37 2.5 -64t11.5 -56.5t24.5 -48.5t43.5 -31t66 -12q64 0 108 24zM924 1072 q0 36 -24 63.5t-60 27.5t-60.5 -27t-24.5 -64q0 -36 25 -62.5t60 -26.5t59.5 27t24.5 62zM1536 1120v-960q0 -119 -84.5 -203.5t-203.5 -84.5h-960q-119 0 -203.5 84.5t-84.5 203.5v960q0 119 84.5 203.5t203.5 84.5h960q119 0 203.5 -84.5t84.5 -203.5z" />
@ -555,7 +555,7 @@
<glyph unicode="&#xf237;" d="M1536 1536l-192 -448h192v-192h-274l-55 -128h329v-192h-411l-357 -832l-357 832h-411v192h329l-55 128h-274v192h192l-192 448h256l323 -768h378l323 768h256zM768 320l108 256h-216z" />
<glyph unicode="&#xf238;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM768 192q80 0 136 56t56 136t-56 136t-136 56 t-136 -56t-56 -136t56 -136t136 -56zM1344 768v512h-1152v-512h1152z" />
<glyph unicode="&#xf239;" d="M1088 1536q185 0 316.5 -93.5t131.5 -226.5v-896q0 -130 -125.5 -222t-305.5 -97l213 -202q16 -15 8 -35t-30 -20h-1056q-22 0 -30 20t8 35l213 202q-180 5 -305.5 97t-125.5 222v896q0 133 131.5 226.5t316.5 93.5h640zM288 224q66 0 113 47t47 113t-47 113t-113 47 t-113 -47t-47 -113t47 -113t113 -47zM704 768v512h-544v-512h544zM1248 224q66 0 113 47t47 113t-47 113t-113 47t-113 -47t-47 -113t47 -113t113 -47zM1408 768v512h-576v-512h576z" />
<glyph unicode="&#xf23a;" horiz-adv-x="1792" d="M1792 204v-209h-642v209h134v926h-6l-314 -1135h-243l-310 1135h-8v-926h135v-209h-538v209h69q21 0 43 19.5t22 37.5v881q0 18 -22 40t-43 22h-69v209h672l221 -821h6l223 821h670v-209h-71q-19 0 -41 -22t-22 -40v-881q0 -18 21.5 -37.5t41.5 -19.5h71z" />
<glyph unicode="&#xf23a;" horiz-adv-x="1792" d="M597 1115v-1173q0 -25 -12.5 -42.5t-36.5 -17.5q-17 0 -33 8l-465 233q-21 10 -35.5 33.5t-14.5 46.5v1140q0 20 10 34t29 14q14 0 44 -15l511 -256q3 -3 3 -5zM661 1014l534 -866l-534 266v600zM1792 996v-1054q0 -25 -14 -40.5t-38 -15.5t-47 13l-441 220zM1789 1116 q0 -3 -256.5 -419.5t-300.5 -487.5l-390 634l324 527q17 28 52 28q14 0 26 -6l541 -270q4 -2 4 -6z" />
<glyph unicode="&#xf23b;" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1408v-1536h-1536v1536h1536z" />
<glyph unicode="&#xf23c;" horiz-adv-x="2296" d="M478 -139q-8 -16 -27 -34.5t-37 -25.5q-25 -9 -51.5 3.5t-28.5 31.5q-1 22 40 55t68 38q23 4 34 -21.5t2 -46.5zM1819 -139q7 -16 26 -34.5t38 -25.5q25 -9 51.5 3.5t27.5 31.5q2 22 -39.5 55t-68.5 38q-22 4 -33 -21.5t-2 -46.5zM1867 -30q13 -27 56.5 -59.5t77.5 -41.5 q45 -13 82 4.5t37 50.5q0 46 -67.5 100.5t-115.5 59.5q-40 5 -63.5 -37.5t-6.5 -76.5zM428 -30q-13 -27 -56 -59.5t-77 -41.5q-45 -13 -82 4.5t-37 50.5q0 46 67.5 100.5t115.5 59.5q40 5 63 -37.5t6 -76.5zM1158 1094h1q-41 0 -76 -15q27 -8 44 -30.5t17 -49.5 q0 -35 -27 -60t-65 -25q-52 0 -80 43q-5 -23 -5 -42q0 -74 56 -126.5t135 -52.5q80 0 136 52.5t56 126.5t-56 126.5t-136 52.5zM1462 1312q-99 109 -220.5 131.5t-245.5 -44.5q27 60 82.5 96.5t118 39.5t121.5 -17t99.5 -74.5t44.5 -131.5zM2212 73q8 -11 -11 -42 q7 -23 7 -40q1 -56 -44.5 -112.5t-109.5 -91.5t-118 -37q-48 -2 -92 21.5t-66 65.5q-687 -25 -1259 0q-23 -41 -66.5 -65t-92.5 -22q-86 3 -179.5 80.5t-92.5 160.5q2 22 7 40q-19 31 -11 42q6 10 31 1q14 22 41 51q-7 29 2 38q11 10 39 -4q29 20 59 34q0 29 13 37 q23 12 51 -16q35 5 61 -2q18 -4 38 -19v73q-11 0 -18 2q-53 10 -97 44.5t-55 87.5q-9 38 0 81q15 62 93 95q2 17 19 35.5t36 23.5t33 -7.5t19 -30.5h13q46 -5 60 -23q3 -3 5 -7q10 1 30.5 3.5t30.5 3.5q-15 11 -30 17q-23 40 -91 43q0 6 1 10q-62 2 -118.5 18.5t-84.5 47.5 q-32 36 -42.5 92t-2.5 112q16 126 90 179q23 16 52 4.5t32 -40.5q0 -1 1.5 -14t2.5 -21t3 -20t5.5 -19t8.5 -10q27 -14 76 -12q48 46 98 74q-40 4 -162 -14l47 46q61 58 163 111q145 73 282 86q-20 8 -41 15.5t-47 14t-42.5 10.5t-47.5 11t-43 10q595 126 904 -139 q98 -84 158 -222q85 -10 121 9h1q5 3 8.5 10t5.5 19t3 19.5t3 21.5l1 14q3 28 32 40t52 -5q73 -52 91 -178q7 -57 -3.5 -113t-42.5 -91q-28 -32 -83.5 -48.5t-115.5 -18.5v-10q-71 -2 -95 -43q-14 -5 -31 -17q11 -1 32 -3.5t30 -3.5q1 4 5 8q16 18 60 23h13q5 18 19 30t33 8 t36 -23t19 -36q79 -32 93 -95q9 -40 1 -81q-12 -53 -56 -88t-97 -44q-10 -2 -17 -2q0 -49 -1 -73q20 15 38 19q26 7 61 2q28 28 51 16q14 -9 14 -37q33 -16 59 -34q27 13 38 4q10 -10 2 -38q28 -30 41 -51q23 8 31 -1zM1937 1025q0 -29 -9 -54q82 -32 112 -132 q4 37 -9.5 98.5t-41.5 90.5q-20 19 -36 17t-16 -20zM1859 925q35 -42 47.5 -108.5t-0.5 -124.5q67 13 97 45q13 14 18 28q-3 64 -31 114.5t-79 66.5q-15 -15 -52 -21zM1822 921q-30 0 -44 1q42 -115 53 -239q21 0 43 3q16 68 1 135t-53 100zM258 839q30 100 112 132 q-9 25 -9 54q0 18 -16.5 20t-35.5 -17q-28 -29 -41.5 -90.5t-9.5 -98.5zM294 737q29 -31 97 -45q-13 58 -0.5 124.5t47.5 108.5v0q-37 6 -52 21q-51 -16 -78.5 -66t-31.5 -115q9 -17 18 -28zM471 683q14 124 73 235q-19 -4 -55 -18l-45 -19v1q-46 -89 -20 -196q25 -3 47 -3z M1434 644q8 -38 16.5 -108.5t11.5 -89.5q3 -18 9.5 -21.5t23.5 4.5q40 20 62 85.5t23 125.5q-24 2 -146 4zM1152 1285q-116 0 -199 -82.5t-83 -198.5q0 -117 83 -199.5t199 -82.5t199 82.5t83 199.5q0 116 -83 198.5t-199 82.5zM1380 646q-106 2 -211 0v1q-1 -27 2.5 -86 t13.5 -66q29 -14 93.5 -14.5t95.5 10.5q9 3 11 39t-0.5 69.5t-4.5 46.5zM1112 447q8 4 9.5 48t-0.5 88t-4 63v1q-212 -3 -214 -3q-4 -20 -7 -62t0 -83t14 -46q34 -15 101 -16t101 10zM718 636q-16 -59 4.5 -118.5t77.5 -84.5q15 -8 24 -5t12 21q3 16 8 90t10 103 q-69 -2 -136 -6zM591 510q3 -23 -34 -36q132 -141 271.5 -240t305.5 -154q172 49 310.5 146t293.5 250q-33 13 -30 34l3 9v1v-1q-17 2 -50 5.5t-48 4.5q-26 -90 -82 -132q-51 -38 -82 1q-5 6 -9 14q-7 13 -17 62q-2 -5 -5 -9t-7.5 -7t-8 -5.5t-9.5 -4l-10 -2.5t-12 -2 l-12 -1.5t-13.5 -1t-13.5 -0.5q-106 -9 -163 11q-4 -17 -10 -26.5t-21 -15t-23 -7t-36 -3.5q-2 0 -3 -0.5t-3 -0.5h-3q-179 -17 -203 40q-2 -63 -56 -54q-47 8 -91 54q-12 13 -20 26q-17 29 -26 65q-58 -6 -87 -10q1 -2 4 -10zM507 -118q3 14 3 30q-17 71 -51 130t-73 70 q-41 12 -101.5 -14.5t-104.5 -80t-39 -107.5q35 -53 100 -93t119 -42q51 -2 94 28t53 79zM510 53q23 -63 27 -119q195 113 392 174q-98 52 -180.5 120t-179.5 165q-6 -4 -29 -13q0 -2 -1 -5t-1 -4q31 -18 22 -37q-12 -23 -56 -34q-10 -13 -29 -24h-1q-2 -83 1 -150 q19 -34 35 -73zM579 -113q532 -21 1145 0q-254 147 -428 196q-76 -35 -156 -57q-8 -3 -16 0q-65 21 -129 49q-208 -60 -416 -188h-1v-1q1 0 1 1zM1763 -67q4 54 28 120q14 38 33 71l-1 -1q3 77 3 153q-15 8 -30 25q-42 9 -56 33q-9 20 22 38q-2 4 -2 9q-16 4 -28 12 q-204 -190 -383 -284q198 -59 414 -176zM2155 -90q5 54 -39 107.5t-104 80t-102 14.5q-38 -11 -72.5 -70.5t-51.5 -129.5q0 -16 3 -30q10 -49 53 -79t94 -28q54 2 119 42t100 93z" />
<glyph unicode="&#xf23d;" horiz-adv-x="2304" d="M1524 -25q0 -68 -48 -116t-116 -48t-116.5 48t-48.5 116t48.5 116.5t116.5 48.5t116 -48.5t48 -116.5zM775 -25q0 -68 -48.5 -116t-116.5 -48t-116 48t-48 116t48 116.5t116 48.5t116.5 -48.5t48.5 -116.5zM0 1469q57 -60 110.5 -104.5t121 -82t136 -63t166 -45.5 t200 -31.5t250 -18.5t304 -9.5t372.5 -2.5q139 0 244.5 -5t181 -16.5t124 -27.5t71 -39.5t24 -51.5t-19.5 -64t-56.5 -76.5t-89.5 -91t-116 -104.5t-139 -119q-185 -157 -286 -247q29 51 76.5 109t94 105.5t94.5 98.5t83 91.5t54 80.5t13 70t-45.5 55.5t-116.5 41t-204 23.5 t-304 5q-168 -2 -314 6t-256 23t-204.5 41t-159.5 51.5t-122.5 62.5t-91.5 66.5t-68 71.5t-50.5 69.5t-40 68t-36.5 59.5z" />
@ -600,11 +600,11 @@
<glyph unicode="&#xf267;" horiz-adv-x="1792" d="M949 643q0 -26 -16.5 -45t-41.5 -19q-26 0 -45 16.5t-19 41.5q0 26 17 45t42 19t44 -16.5t19 -41.5zM964 585l350 581q-9 -8 -67.5 -62.5t-125.5 -116.5t-136.5 -127t-117 -110.5t-50.5 -51.5l-349 -580q7 7 67 62t126 116.5t136 127t117 111t50 50.5zM1611 640 q0 -201 -104 -371q-3 2 -17 11t-26.5 16.5t-16.5 7.5q-13 0 -13 -13q0 -10 59 -44q-74 -112 -184.5 -190.5t-241.5 -110.5l-16 67q-1 10 -15 10q-5 0 -8 -5.5t-2 -9.5l16 -68q-72 -15 -146 -15q-199 0 -372 105q1 2 13 20.5t21.5 33.5t9.5 19q0 13 -13 13q-6 0 -17 -14.5 t-22.5 -34.5t-13.5 -23q-113 75 -192 187.5t-110 244.5l69 15q10 3 10 15q0 5 -5.5 8t-10.5 2l-68 -15q-14 72 -14 139q0 206 109 379q2 -1 18.5 -12t30 -19t17.5 -8q13 0 13 12q0 6 -12.5 15.5t-32.5 21.5l-20 12q77 112 189 189t244 107l15 -67q2 -10 15 -10q5 0 8 5.5 t2 10.5l-15 66q71 13 134 13q204 0 379 -109q-39 -56 -39 -65q0 -13 12 -13q11 0 48 64q111 -75 187.5 -186t107.5 -241l-56 -12q-10 -2 -10 -16q0 -5 5.5 -8t9.5 -2l57 13q14 -72 14 -140zM1696 640q0 163 -63.5 311t-170.5 255t-255 170.5t-311 63.5t-311 -63.5 t-255 -170.5t-170.5 -255t-63.5 -311t63.5 -311t170.5 -255t255 -170.5t311 -63.5t311 63.5t255 170.5t170.5 255t63.5 311zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71t348 -71t286 -191 t191 -286t71 -348z" />
<glyph unicode="&#xf268;" horiz-adv-x="1792" d="M893 1536q240 2 451 -120q232 -134 352 -372l-742 39q-160 9 -294 -74.5t-185 -229.5l-276 424q128 159 311 245.5t383 87.5zM146 1131l337 -663q72 -143 211 -217t293 -45l-230 -451q-212 33 -385 157.5t-272.5 316t-99.5 411.5q0 267 146 491zM1732 962 q58 -150 59.5 -310.5t-48.5 -306t-153 -272t-246 -209.5q-230 -133 -498 -119l405 623q88 131 82.5 290.5t-106.5 277.5zM896 942q125 0 213.5 -88.5t88.5 -213.5t-88.5 -213.5t-213.5 -88.5t-213.5 88.5t-88.5 213.5t88.5 213.5t213.5 88.5z" />
<glyph unicode="&#xf269;" horiz-adv-x="1792" d="M903 -256q-283 0 -504.5 150.5t-329.5 398.5q-58 131 -67 301t26 332.5t111 312t179 242.5l-11 -281q11 14 68 15.5t70 -15.5q42 81 160.5 138t234.5 59q-54 -45 -119.5 -148.5t-58.5 -163.5q25 -8 62.5 -13.5t63 -7.5t68 -4t50.5 -3q15 -5 9.5 -45.5t-30.5 -75.5 q-5 -7 -16.5 -18.5t-56.5 -35.5t-101 -34l15 -189l-139 67q-18 -43 -7.5 -81.5t36 -66.5t65.5 -41.5t81 -6.5q51 9 98 34.5t83.5 45t73.5 17.5q61 -4 89.5 -33t19.5 -65q-1 -2 -2.5 -5.5t-8.5 -12.5t-18 -15.5t-31.5 -10.5t-46.5 -1q-60 -95 -144.5 -135.5t-209.5 -29.5 q74 -61 162.5 -82.5t168.5 -6t154.5 52t128 87.5t80.5 104q43 91 39 192.5t-37.5 188.5t-78.5 125q87 -38 137 -79.5t77 -112.5q15 170 -57.5 343t-209.5 284q265 -77 412 -279.5t151 -517.5q2 -127 -40.5 -255t-123.5 -238t-189 -196t-247.5 -135.5t-288.5 -49.5z" />
<glyph unicode="&#xf26a;" d="M768 -92q77 0 139.5 63t100.5 166t59 234.5t21 268.5t-21 268.5t-59 234.5t-100.5 166t-139.5 63t-139.5 -63t-100.5 -166t-59 -234.5t-21 -268.5t21 -268.5t59 -234.5t100.5 -166t139.5 -63zM768 -256q-184 0 -333 77t-240 203t-141 287t-50 329t50 329t141 287t240 203 t333 77q148 0 274 -50t214.5 -136t151.5 -201t92.5 -244t29.5 -265t-29.5 -265t-92.5 -244t-151.5 -201t-214.5 -136t-274 -50z" />
<glyph unicode="&#xf26b;" horiz-adv-x="1792" d="M716 -69q-143 35 -261.5 114t-197.5 191q-139 -300 -17 -398q26 -21 85 -24.5t127.5 9.5t141 41.5t122.5 66.5zM693 762h452q0 108 -61.5 169t-168.5 61q-103 0 -162.5 -62.5t-59.5 -167.5zM1724 1137h-34q26 102 22.5 170t-25 110t-63.5 57t-93.5 11t-115 -26.5 t-128.5 -56.5t-134 -79q129 -37 238.5 -113.5t185 -179t110 -231.5t15.5 -262h-1005q0 -60 10 -106t34 -85t69.5 -60t112.5 -21q87 0 142.5 44t72.5 122h540q-71 -230 -281.5 -377t-477.5 -147q-83 0 -159 15q-35 -40 -151 -94t-248 -78t-219 35q-78 60 -100 159t7 214 t88 242t143.5 248t173.5 226.5t177.5 183.5t156.5 112v24q-120 -37 -258.5 -137.5t-240.5 -207t-159 -195.5q4 106 34 201t80 169t118 135.5t147.5 100.5t168 65.5t180.5 29.5t185 -8q310 186 503 189h7q57 0 103 -18q80 -30 98 -132.5t-30 -248.5z" />
<glyph unicode="&#xf26a;" horiz-adv-x="1792" d="M1493 1308q-165 110 -359 110q-155 0 -293 -73t-240 -200q-75 -93 -119.5 -218t-48.5 -266v-42q4 -141 48.5 -266t119.5 -218q102 -127 240 -200t293 -73q194 0 359 110q-121 -108 -274.5 -168t-322.5 -60q-29 0 -43 1q-175 8 -333 82t-272 193t-181 281t-67 339 q0 182 71 348t191 286t286 191t348 71h3q168 -1 320.5 -60.5t273.5 -167.5zM1792 640q0 -192 -77 -362.5t-213 -296.5q-104 -63 -222 -63q-137 0 -255 84q154 56 253.5 233t99.5 405q0 227 -99 404t-253 234q119 83 254 83q119 0 226 -65q135 -125 210.5 -295t75.5 -361z " />
<glyph unicode="&#xf26b;" horiz-adv-x="1792" d="M1792 599q0 -56 -7 -104h-1151q0 -146 109.5 -244.5t257.5 -98.5q99 0 185.5 46.5t136.5 130.5h423q-56 -159 -170.5 -281t-267.5 -188.5t-321 -66.5q-187 0 -356 83q-228 -116 -394 -116q-237 0 -237 263q0 115 45 275q17 60 109 229q199 360 475 606 q-184 -79 -427 -354q63 274 283.5 449.5t501.5 175.5q30 0 45 -1q255 117 433 117q64 0 116 -13t94.5 -40.5t66.5 -76.5t24 -115q0 -116 -75 -286q101 -182 101 -390zM1722 1239q0 83 -53 132t-137 49q-108 0 -254 -70q121 -47 222.5 -131.5t170.5 -195.5q51 135 51 216z M128 2q0 -86 48.5 -132.5t134.5 -46.5q115 0 266 83q-122 72 -213.5 183t-137.5 245q-98 -205 -98 -332zM632 715h728q-5 142 -113 237t-251 95q-144 0 -251.5 -95t-112.5 -237z" />
<glyph unicode="&#xf26c;" horiz-adv-x="2048" d="M1792 288v960q0 13 -9.5 22.5t-22.5 9.5h-1600q-13 0 -22.5 -9.5t-9.5 -22.5v-960q0 -13 9.5 -22.5t22.5 -9.5h1600q13 0 22.5 9.5t9.5 22.5zM1920 1248v-960q0 -66 -47 -113t-113 -47h-736v-128h352q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-832q-14 0 -23 9t-9 23 v64q0 14 9 23t23 9h352v128h-736q-66 0 -113 47t-47 113v960q0 66 47 113t113 47h1600q66 0 113 -47t47 -113z" />
<glyph unicode="&#xf26d;" horiz-adv-x="1792" d="M138 1408h197q-70 -64 -126 -149q-36 -56 -59 -115t-30 -125.5t-8.5 -120t10.5 -132t21 -126t28 -136.5q4 -19 6 -28q51 -238 81 -329q57 -171 152 -275h-272q-48 0 -82 34t-34 82v1304q0 48 34 82t82 34zM1346 1408h308q48 0 82 -34t34 -82v-1304q0 -48 -34 -82t-82 -34 h-178q212 210 196 565l-469 -101q-2 -45 -12 -82t-31 -72t-59.5 -59.5t-93.5 -36.5q-123 -26 -199 40q-32 27 -53 61t-51.5 129t-64.5 258q-35 163 -45.5 263t-5.5 139t23 77q20 41 62.5 73t102.5 45q45 12 83.5 6.5t67 -17t54 -35t43 -48t34.5 -56.5l468 100 q-68 175 -180 287z" />
<glyph unicode="&#xf26e;" horiz-adv-x="2304" d="M1391 390v0l-1 1q-15 18 -34.5 37.5t-62.5 57.5t-93.5 62t-95.5 24q-48 0 -83 -21.5t-51 -54t-23 -59t-7 -47.5v0v0q0 -21 7 -48t23 -59t51 -53.5t83 -21.5q45 0 95.5 24t94 62.5t62 57t34.5 37.5zM2103 390q0 21 -7 47.5t-23 59t-51 54t-83 21.5q-45 0 -95.5 -24 t-94 -62.5t-62 -57t-34.5 -37.5l-1 -1v0v0l1 -1q15 -18 34.5 -37.5t62.5 -57.5t93.5 -62t95.5 -24q48 0 83 21.5t51 53.5t23 59t7 48zM2304 393q0 -69 -24 -137.5t-68 -126t-116 -93.5t-159 -36q-68 0 -134 24t-113.5 58.5t-84.5 69.5t-59.5 59t-25.5 24t-22.5 -24 t-54.5 -58.5t-81.5 -69.5t-115 -59t-143.5 -24q-65 0 -123.5 22.5t-96.5 54t-66.5 66.5t-41 59.5t-12.5 32.5q0 -8 -8.5 -26.5t-25 -45.5t-47 -55t-69 -52.5t-96.5 -40t-125 -15.5q-71 0 -130 15.5t-98.5 39.5t-70.5 56.5t-48 63.5t-27.5 63.5t-14 54t-3.5 36.5h217 q0 -55 49 -107.5t126 -52.5q79 0 134.5 67t55.5 148q0 80 -52 136.5t-138 56.5q-5 0 -13 -0.5t-31 -5t-43 -12t-42 -24.5t-34 -40h-195l102 583h602v-174h-445q-27 -159 -41 -248q4 0 16.5 13t31.5 28.5t65 28.5t108 13t114 -20.5t82.5 -49.5t51.5 -58.5t31 -50t11 -20.5 t13 25t36.5 60.5t60.5 71.5t97 61t133 25t140.5 -25t115.5 -60.5t83.5 -71.5t56.5 -61t21 -25q2 0 22 25t56 60.5t83.5 71.5t115.5 61t140 25q92 0 164.5 -35t115.5 -93t65 -125t22 -137z" />
<glyph unicode="&#xf26e;" d="M1401 -11l-6 -6q-113 -114 -259 -175q-154 -64 -317 -64q-165 0 -317 64q-148 63 -259 175q-113 112 -175 258q-42 103 -54 189q-4 28 48 36q51 8 56 -20q1 -1 1 -4q18 -90 46 -159q50 -124 152 -226q98 -98 226 -152q132 -56 276 -56q143 0 276 56q128 55 225 152l6 6 q10 10 25 6q12 -3 33 -22q36 -37 17 -58zM929 604l-66 -66l63 -63q21 -21 -7 -49q-17 -17 -32 -17q-10 0 -19 10l-62 61l-66 -66q-5 -5 -15 -5q-15 0 -31 16l-2 2q-18 15 -18 29q0 7 8 17l66 65l-66 66q-16 16 14 45q18 18 31 18q6 0 13 -5l65 -66l65 65q18 17 48 -13 q27 -27 11 -44zM1400 547q0 -118 -46 -228q-45 -105 -126 -186q-80 -80 -187 -126t-228 -46t-228 46t-187 126q-82 82 -125 186q-15 32 -15 40h-1q-9 27 43 44q50 16 60 -12q37 -99 97 -167h1v339v2q3 136 102 232q105 103 253 103q147 0 251 -103t104 -249 q0 -147 -104.5 -251t-250.5 -104q-58 0 -112 16q-28 11 -13 61q16 51 44 43l14 -3q14 -3 32.5 -6t30.5 -3q104 0 176 71.5t72 174.5q0 101 -72 171q-71 71 -175 71q-107 0 -178 -80q-64 -72 -64 -160v-413q110 -67 242 -67q96 0 185 36.5t156 103.5t103.5 155t36.5 183 q0 198 -141 339q-140 140 -339 140q-200 0 -340 -140q-53 -53 -77 -87l-2 -2q-8 -11 -13 -15.5t-21.5 -9.5t-38.5 3q-21 5 -36.5 16.5t-15.5 26.5v680q0 15 10.5 26.5t27.5 11.5h877q30 0 30 -55t-30 -55h-811v-483h1q40 42 102 84t108 61q109 46 231 46q121 0 228 -46 t187 -126q81 -81 126 -186q46 -112 46 -229zM1369 1128q9 -8 9 -18t-5.5 -18t-16.5 -21q-26 -26 -39 -26q-9 0 -16 7q-106 91 -207 133q-128 56 -276 56q-133 0 -262 -49q-27 -10 -45 37q-9 25 -8 38q3 16 16 20q130 57 299 57q164 0 316 -64q137 -58 235 -152z" />
<glyph unicode="&#xf270;" horiz-adv-x="1792" d="M1551 60q15 6 26 3t11 -17.5t-15 -33.5q-13 -16 -44 -43.5t-95.5 -68t-141 -74t-188 -58t-229.5 -24.5q-119 0 -238 31t-209 76.5t-172.5 104t-132.5 105t-84 87.5q-8 9 -10 16.5t1 12t8 7t11.5 2t11.5 -4.5q192 -117 300 -166q389 -176 799 -90q190 40 391 135z M1758 175q11 -16 2.5 -69.5t-28.5 -102.5q-34 -83 -85 -124q-17 -14 -26 -9t0 24q21 45 44.5 121.5t6.5 98.5q-5 7 -15.5 11.5t-27 6t-29.5 2.5t-35 0t-31.5 -2t-31 -3t-22.5 -2q-6 -1 -13 -1.5t-11 -1t-8.5 -1t-7 -0.5h-5.5h-4.5t-3 0.5t-2 1.5l-1.5 3q-6 16 47 40t103 30 q46 7 108 1t76 -24zM1364 618q0 -31 13.5 -64t32 -58t37.5 -46t33 -32l13 -11l-227 -224q-40 37 -79 75.5t-58 58.5l-19 20q-11 11 -25 33q-38 -59 -97.5 -102.5t-127.5 -63.5t-140 -23t-137.5 21t-117.5 65.5t-83 113t-31 162.5q0 84 28 154t72 116.5t106.5 83t122.5 57 t130 34.5t119.5 18.5t99.5 6.5v127q0 65 -21 97q-34 53 -121 53q-6 0 -16.5 -1t-40.5 -12t-56 -29.5t-56 -59.5t-48 -96l-294 27q0 60 22 119t67 113t108 95t151.5 65.5t190.5 24.5q100 0 181 -25t129.5 -61.5t81 -83t45 -86t12.5 -73.5v-589zM692 597q0 -86 70 -133 q66 -44 139 -22q84 25 114 123q14 45 14 101v162q-59 -2 -111 -12t-106.5 -33.5t-87 -71t-32.5 -114.5z" />
<glyph unicode="&#xf271;" horiz-adv-x="1792" d="M1536 1280q52 0 90 -38t38 -90v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h128zM1152 1376v-288q0 -14 9 -23t23 -9 h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM384 1376v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23zM1536 -128v1024h-1408v-1024h1408zM896 448h224q14 0 23 -9t9 -23v-64q0 -14 -9 -23t-23 -9h-224 v-224q0 -14 -9 -23t-23 -9h-64q-14 0 -23 9t-9 23v224h-224q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h224v224q0 14 9 23t23 9h64q14 0 23 -9t9 -23v-224z" />
<glyph unicode="&#xf272;" horiz-adv-x="1792" d="M1152 416v-64q0 -14 -9 -23t-23 -9h-576q-14 0 -23 9t-9 23v64q0 14 9 23t23 9h576q14 0 23 -9t9 -23zM128 -128h1408v1024h-1408v-1024zM512 1088v288q0 14 -9 23t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1280 1088v288q0 14 -9 23 t-23 9h-64q-14 0 -23 -9t-9 -23v-288q0 -14 9 -23t23 -9h64q14 0 23 9t9 23zM1664 1152v-1280q0 -52 -38 -90t-90 -38h-1408q-52 0 -90 38t-38 90v1280q0 52 38 90t90 38h128v96q0 66 47 113t113 47h64q66 0 113 -47t47 -113v-96h384v96q0 66 47 113t113 47h64q66 0 113 -47 t47 -113v-96h128q52 0 90 -38t38 -90z" />
@ -621,20 +621,35 @@
<glyph unicode="&#xf27d;" horiz-adv-x="1792" d="M1709 1018q-10 -236 -332 -651q-333 -431 -562 -431q-142 0 -240 263q-44 160 -132 482q-72 262 -157 262q-18 0 -127 -76l-77 98q24 21 108 96.5t130 115.5q156 138 241 146q95 9 153 -55.5t81 -203.5q44 -287 66 -373q55 -249 120 -249q51 0 154 161q101 161 109 246 q13 139 -109 139q-57 0 -121 -26q120 393 459 382q251 -8 236 -326z" />
<glyph unicode="&#xf27e;" d="M0 1408h1536v-1536h-1536v1536zM1085 293l-221 631l221 297h-634l221 -297l-221 -631l317 -304z" />
<glyph unicode="&#xf280;" d="M0 1408h1536v-1536h-1536v1536zM908 1088l-12 -33l75 -83l-31 -114l25 -25l107 57l107 -57l25 25l-31 114l75 83l-12 33h-95l-53 96h-32l-53 -96h-95zM641 925q32 0 44.5 -16t11.5 -63l174 21q0 55 -17.5 92.5t-50.5 56t-69 25.5t-85 7q-133 0 -199 -57.5t-66 -182.5v-72 h-96v-128h76q20 0 20 -8v-382q0 -14 -5 -20t-18 -7l-73 -7v-88h448v86l-149 14q-6 1 -8.5 1.5t-3.5 2.5t-0.5 4t1 7t0.5 10v387h191l38 128h-231q-6 0 -2 6t4 9v80q0 27 1.5 40.5t7.5 28t19.5 20t36.5 5.5zM1248 96v86l-54 9q-7 1 -9.5 2.5t-2.5 3t1 7.5t1 12v520h-275 l-23 -101l83 -22q23 -7 23 -27v-370q0 -14 -6 -18.5t-20 -6.5l-70 -9v-86h352z" />
<glyph unicode="&#xf281;" horiz-adv-x="1792" />
<glyph unicode="&#xf282;" horiz-adv-x="1792" />
<glyph unicode="&#xf283;" horiz-adv-x="1792" />
<glyph unicode="&#xf284;" horiz-adv-x="1792" />
<glyph unicode="&#xf285;" horiz-adv-x="1792" />
<glyph unicode="&#xf286;" horiz-adv-x="1792" />
<glyph unicode="&#xf287;" horiz-adv-x="1792" />
<glyph unicode="&#xf288;" horiz-adv-x="1792" />
<glyph unicode="&#xf289;" horiz-adv-x="1792" />
<glyph unicode="&#xf28a;" horiz-adv-x="1792" />
<glyph unicode="&#xf28b;" horiz-adv-x="1792" />
<glyph unicode="&#xf28c;" horiz-adv-x="1792" />
<glyph unicode="&#xf28d;" horiz-adv-x="1792" />
<glyph unicode="&#xf28e;" horiz-adv-x="1792" />
<glyph unicode="&#xf281;" horiz-adv-x="1792" d="M1792 690q0 -58 -29.5 -105.5t-79.5 -72.5q12 -46 12 -96q0 -155 -106.5 -287t-290.5 -208.5t-400 -76.5t-399.5 76.5t-290 208.5t-106.5 287q0 47 11 94q-51 25 -82 73.5t-31 106.5q0 82 58 140.5t141 58.5q85 0 145 -63q218 152 515 162l116 521q3 13 15 21t26 5 l369 -81q18 37 54 59.5t79 22.5q62 0 106 -43.5t44 -105.5t-44 -106t-106 -44t-105.5 43.5t-43.5 105.5l-334 74l-104 -472q300 -9 519 -160q58 61 143 61q83 0 141 -58.5t58 -140.5zM418 491q0 -62 43.5 -106t105.5 -44t106 44t44 106t-44 105.5t-106 43.5q-61 0 -105 -44 t-44 -105zM1228 136q11 11 11 26t-11 26q-10 10 -25 10t-26 -10q-41 -42 -121 -62t-160 -20t-160 20t-121 62q-11 10 -26 10t-25 -10q-11 -10 -11 -25.5t11 -26.5q43 -43 118.5 -68t122.5 -29.5t91 -4.5t91 4.5t122.5 29.5t118.5 68zM1225 341q62 0 105.5 44t43.5 106 q0 61 -44 105t-105 44q-62 0 -106 -43.5t-44 -105.5t44 -106t106 -44z" />
<glyph unicode="&#xf282;" horiz-adv-x="1792" d="M69 741h1q16 126 58.5 241.5t115 217t167.5 176t223.5 117.5t276.5 43q231 0 414 -105.5t294 -303.5q104 -187 104 -442v-188h-1125q1 -111 53.5 -192.5t136.5 -122.5t189.5 -57t213 -3t208 46.5t173.5 84.5v-377q-92 -55 -229.5 -92t-312.5 -38t-316 53 q-189 73 -311.5 249t-124.5 372q-3 242 111 412t325 268q-48 -60 -78 -125.5t-46 -159.5h635q8 77 -8 140t-47 101.5t-70.5 66.5t-80.5 41t-75 20.5t-56 8.5l-22 1q-135 -5 -259.5 -44.5t-223.5 -104.5t-176 -140.5t-138 -163.5z" />
<glyph unicode="&#xf283;" horiz-adv-x="2304" d="M0 32v608h2304v-608q0 -66 -47 -113t-113 -47h-1984q-66 0 -113 47t-47 113zM640 256v-128h384v128h-384zM256 256v-128h256v128h-256zM2144 1408q66 0 113 -47t47 -113v-224h-2304v224q0 66 47 113t113 47h1984z" />
<glyph unicode="&#xf284;" horiz-adv-x="1792" d="M1549 857q55 0 85.5 -28.5t30.5 -83.5t-34 -82t-91 -27h-136v-177h-25v398h170zM1710 267l-4 -11l-5 -10q-113 -230 -330.5 -366t-474.5 -136q-182 0 -348 71t-286 191t-191 286t-71 348t71 348t191 286t286 191t348 71q244 0 454.5 -124t329.5 -338l2 -4l8 -16 q-30 -15 -136.5 -68.5t-163.5 -84.5q-6 -3 -479 -268q384 -183 799 -366zM896 -234q250 0 462.5 132.5t322.5 357.5l-287 129q-72 -140 -206 -222t-292 -82q-151 0 -280 75t-204 204t-75 280t75 280t204 204t280 75t280 -73.5t204 -204.5l280 143q-116 208 -321 329 t-443 121q-119 0 -232.5 -31.5t-209 -87.5t-176.5 -137t-137 -176.5t-87.5 -209t-31.5 -232.5t31.5 -232.5t87.5 -209t137 -176.5t176.5 -137t209 -87.5t232.5 -31.5z" />
<glyph unicode="&#xf285;" horiz-adv-x="1792" d="M1427 827l-614 386l92 151h855zM405 562l-184 116v858l1183 -743zM1424 697l147 -95v-858l-532 335zM1387 718l-500 -802h-855l356 571z" />
<glyph unicode="&#xf286;" horiz-adv-x="1792" d="M640 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1152 528v224q0 16 -16 16h-96q-16 0 -16 -16v-224q0 -16 16 -16h96q16 0 16 16zM1664 496v-752h-640v320q0 80 -56 136t-136 56t-136 -56t-56 -136v-320h-640v752q0 16 16 16h96 q16 0 16 -16v-112h128v624q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h16v393q-32 19 -32 55q0 26 19 45t45 19t45 -19t19 -45q0 -36 -32 -55v-9h272q16 0 16 -16v-224q0 -16 -16 -16h-272v-128h16q16 0 16 -16v-112h128 v112q0 16 16 16h96q16 0 16 -16v-112h128v112q0 16 16 16h96q16 0 16 -16v-624h128v112q0 16 16 16h96q16 0 16 -16z" />
<glyph unicode="&#xf287;" horiz-adv-x="2304" d="M2288 731q16 -8 16 -27t-16 -27l-320 -192q-8 -5 -16 -5q-9 0 -16 4q-16 10 -16 28v128h-858q37 -58 83 -165q16 -37 24.5 -55t24 -49t27 -47t27 -34t31.5 -26t33 -8h96v96q0 14 9 23t23 9h320q14 0 23 -9t9 -23v-320q0 -14 -9 -23t-23 -9h-320q-14 0 -23 9t-9 23v96h-96 q-32 0 -61 10t-51 23.5t-45 40.5t-37 46t-33.5 57t-28.5 57.5t-28 60.5q-23 53 -37 81.5t-36 65t-44.5 53.5t-46.5 17h-360q-22 -84 -91 -138t-157 -54q-106 0 -181 75t-75 181t75 181t181 75q88 0 157 -54t91 -138h104q24 0 46.5 17t44.5 53.5t36 65t37 81.5q19 41 28 60.5 t28.5 57.5t33.5 57t37 46t45 40.5t51 23.5t61 10h107q21 57 70 92.5t111 35.5q80 0 136 -56t56 -136t-56 -136t-136 -56q-62 0 -111 35.5t-70 92.5h-107q-17 0 -33 -8t-31.5 -26t-27 -34t-27 -47t-24 -49t-24.5 -55q-46 -107 -83 -165h1114v128q0 18 16 28t32 -1z" />
<glyph unicode="&#xf288;" horiz-adv-x="1792" d="M1150 774q0 -56 -39.5 -95t-95.5 -39h-253v269h253q56 0 95.5 -39.5t39.5 -95.5zM1329 774q0 130 -91.5 222t-222.5 92h-433v-896h180v269h253q130 0 222 91.5t92 221.5zM1792 640q0 -182 -71 -348t-191 -286t-286 -191t-348 -71t-348 71t-286 191t-191 286t-71 348 t71 348t191 286t286 191t348 71t348 -71t286 -191t191 -286t71 -348z" />
<glyph unicode="&#xf289;" horiz-adv-x="2304" d="M1645 438q0 59 -34 106.5t-87 68.5q-7 -45 -23 -92q-7 -24 -27.5 -38t-44.5 -14q-12 0 -24 3q-31 10 -45 38.5t-4 58.5q23 71 23 143q0 123 -61 227.5t-166 165.5t-228 61q-134 0 -247 -73t-167 -194q108 -28 188 -106q22 -23 22 -55t-22 -54t-54 -22t-55 22 q-75 75 -180 75q-106 0 -181 -74.5t-75 -180.5t75 -180.5t181 -74.5h1046q79 0 134.5 55.5t55.5 133.5zM1798 438q0 -142 -100.5 -242t-242.5 -100h-1046q-169 0 -289 119.5t-120 288.5q0 153 100 267t249 136q62 184 221 298t354 114q235 0 408.5 -158.5t196.5 -389.5 q116 -25 192.5 -118.5t76.5 -214.5zM2048 438q0 -175 -97 -319q-23 -33 -64 -33q-24 0 -43 13q-26 17 -32 48.5t12 57.5q71 104 71 233t-71 233q-18 26 -12 57t32 49t57.5 11.5t49.5 -32.5q97 -142 97 -318zM2304 438q0 -244 -134 -443q-23 -34 -64 -34q-23 0 -42 13 q-26 18 -32.5 49t11.5 57q108 164 108 358q0 195 -108 357q-18 26 -11.5 57.5t32.5 48.5q26 18 57 12t49 -33q134 -198 134 -442z" />
<glyph unicode="&#xf28a;" d="M1500 -13q0 -89 -63 -152.5t-153 -63.5t-153.5 63.5t-63.5 152.5q0 90 63.5 153.5t153.5 63.5t153 -63.5t63 -153.5zM1267 268q-115 -15 -192.5 -102.5t-77.5 -205.5q0 -74 33 -138q-146 -78 -379 -78q-109 0 -201 21t-153.5 54.5t-110.5 76.5t-76 85t-44.5 83 t-23.5 66.5t-6 39.5q0 19 4.5 42.5t18.5 56t36.5 58t64 43.5t94.5 18t94 -17.5t63 -41t35.5 -53t17.5 -49t4 -33.5q0 -34 -23 -81q28 -27 82 -42t93 -17l40 -1q115 0 190 51t75 133q0 26 -9 48.5t-31.5 44.5t-49.5 41t-74 44t-93.5 47.5t-119.5 56.5q-28 13 -43 20 q-116 55 -187 100t-122.5 102t-72 125.5t-20.5 162.5q0 78 20.5 150t66 137.5t112.5 114t166.5 77t221.5 28.5q120 0 220 -26t164.5 -67t109.5 -94t64 -105.5t19 -103.5q0 -46 -15 -82.5t-36.5 -58t-48.5 -36t-49 -19.5t-39 -5h-8h-32t-39 5t-44 14t-41 28t-37 46t-24 70.5 t-10 97.5q-15 16 -59 25.5t-81 10.5l-37 1q-68 0 -117.5 -31t-70.5 -70t-21 -76q0 -24 5 -43t24 -46t53 -51t97 -53.5t150 -58.5q76 -25 138.5 -53.5t109 -55.5t83 -59t60.5 -59.5t41 -62.5t26.5 -62t14.5 -63.5t6 -62t1 -62.5z" />
<glyph unicode="&#xf28b;" d="M704 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1152 352v576q0 14 -9 23t-23 9h-256q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h256q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103 t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5t103 -385.5z" />
<glyph unicode="&#xf28c;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM864 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h192q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-192z" />
<glyph unicode="&#xf28d;" d="M1088 352v576q0 14 -9 23t-23 9h-576q-14 0 -23 -9t-9 -23v-576q0 -14 9 -23t23 -9h576q14 0 23 9t9 23zM1536 640q0 -209 -103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103t385.5 -103t279.5 -279.5 t103 -385.5z" />
<glyph unicode="&#xf28e;" d="M768 1408q209 0 385.5 -103t279.5 -279.5t103 -385.5t-103 -385.5t-279.5 -279.5t-385.5 -103t-385.5 103t-279.5 279.5t-103 385.5t103 385.5t279.5 279.5t385.5 103zM768 96q148 0 273 73t198 198t73 273t-73 273t-198 198t-273 73t-273 -73t-198 -198t-73 -273 t73 -273t198 -198t273 -73zM480 320q-14 0 -23 9t-9 23v576q0 14 9 23t23 9h576q14 0 23 -9t9 -23v-576q0 -14 -9 -23t-23 -9h-576z" />
<glyph unicode="&#xf290;" horiz-adv-x="1792" d="M1757 128l35 -313q3 -28 -16 -50q-19 -21 -48 -21h-1664q-29 0 -48 21q-19 22 -16 50l35 313h1722zM1664 967l86 -775h-1708l86 775q3 24 21 40.5t43 16.5h256v-128q0 -53 37.5 -90.5t90.5 -37.5t90.5 37.5t37.5 90.5v128h384v-128q0 -53 37.5 -90.5t90.5 -37.5 t90.5 37.5t37.5 90.5v128h256q25 0 43 -16.5t21 -40.5zM1280 1152v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 106 -75 181t-181 75t-181 -75t-75 -181v-256q0 -26 -19 -45t-45 -19t-45 19t-19 45v256q0 159 112.5 271.5t271.5 112.5t271.5 -112.5t112.5 -271.5z" />
<glyph unicode="&#xf291;" horiz-adv-x="2048" d="M1920 768q53 0 90.5 -37.5t37.5 -90.5t-37.5 -90.5t-90.5 -37.5h-15l-115 -662q-8 -46 -44 -76t-82 -30h-1280q-46 0 -82 30t-44 76l-115 662h-15q-53 0 -90.5 37.5t-37.5 90.5t37.5 90.5t90.5 37.5h1792zM485 -32q26 2 43.5 22.5t15.5 46.5l-32 416q-2 26 -22.5 43.5 t-46.5 15.5t-43.5 -22.5t-15.5 -46.5l32 -416q2 -25 20.5 -42t43.5 -17h5zM896 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1280 32v416q0 26 -19 45t-45 19t-45 -19t-19 -45v-416q0 -26 19 -45t45 -19t45 19t19 45zM1632 27l32 416 q2 26 -15.5 46.5t-43.5 22.5t-46.5 -15.5t-22.5 -43.5l-32 -416q-2 -26 15.5 -46.5t43.5 -22.5h5q25 0 43.5 17t20.5 42zM476 1244l-93 -412h-132l101 441q19 88 89 143.5t160 55.5h167q0 26 19 45t45 19h384q26 0 45 -19t19 -45h167q90 0 160 -55.5t89 -143.5l101 -441 h-132l-93 412q-11 44 -45.5 72t-79.5 28h-167q0 -26 -19 -45t-45 -19h-384q-26 0 -45 19t-19 45h-167q-45 0 -79.5 -28t-45.5 -72z" />
<glyph unicode="&#xf292;" horiz-adv-x="1792" d="M991 512l64 256h-254l-64 -256h254zM1759 1016l-56 -224q-7 -24 -31 -24h-327l-64 -256h311q15 0 25 -12q10 -14 6 -28l-56 -224q-5 -24 -31 -24h-327l-81 -328q-7 -24 -31 -24h-224q-16 0 -26 12q-9 12 -6 28l78 312h-254l-81 -328q-7 -24 -31 -24h-225q-15 0 -25 12 q-9 12 -6 28l78 312h-311q-15 0 -25 12q-9 12 -6 28l56 224q7 24 31 24h327l64 256h-311q-15 0 -25 12q-10 14 -6 28l56 224q5 24 31 24h327l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h254l81 328q7 24 32 24h224q15 0 25 -12q9 -12 6 -28l-78 -312h311 q15 0 25 -12q9 -12 6 -28z" />
<glyph unicode="&#xf293;" d="M841 483l148 -148l-149 -149zM840 1094l149 -149l-148 -148zM710 -130l464 464l-306 306l306 306l-464 464v-611l-255 255l-93 -93l320 -321l-320 -321l93 -93l255 255v-611zM1429 640q0 -209 -32 -365.5t-87.5 -257t-140.5 -162.5t-181.5 -86.5t-219.5 -24.5 t-219.5 24.5t-181.5 86.5t-140.5 162.5t-87.5 257t-32 365.5t32 365.5t87.5 257t140.5 162.5t181.5 86.5t219.5 24.5t219.5 -24.5t181.5 -86.5t140.5 -162.5t87.5 -257t32 -365.5z" />
<glyph unicode="&#xf294;" horiz-adv-x="1024" d="M596 113l173 172l-173 172v-344zM596 823l173 172l-173 172v-344zM628 640l356 -356l-539 -540v711l-297 -296l-108 108l372 373l-372 373l108 108l297 -296v711l539 -540z" />
<glyph unicode="&#xf295;" d="M1280 256q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM512 1024q0 52 -38 90t-90 38t-90 -38t-38 -90t38 -90t90 -38t90 38t38 90zM1536 256q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5t271.5 -112.5 t112.5 -271.5zM1440 1344q0 -20 -13 -38l-1056 -1408q-19 -26 -51 -26h-160q-26 0 -45 19t-19 45q0 20 13 38l1056 1408q19 26 51 26h160q26 0 45 -19t19 -45zM768 1024q0 -159 -112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5 t271.5 -112.5t112.5 -271.5z" />
<glyph unicode="&#xf296;" horiz-adv-x="1792" />
<glyph unicode="&#xf297;" horiz-adv-x="1792" />
<glyph unicode="&#xf298;" horiz-adv-x="1792" />
<glyph unicode="&#xf299;" horiz-adv-x="1792" />
<glyph unicode="&#xf29a;" horiz-adv-x="1792" />
<glyph unicode="&#xf29b;" horiz-adv-x="1792" />
<glyph unicode="&#xf29c;" horiz-adv-x="1792" />
<glyph unicode="&#xf29d;" horiz-adv-x="1792" />
<glyph unicode="&#xf29e;" horiz-adv-x="1792" />
<glyph unicode="&#xf500;" horiz-adv-x="1792" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 348 KiB

After

Width:  |  Height:  |  Size: 357 KiB

View File

@ -1,2 +0,0 @@
import CustomizationBase from 'admin/adapters/customization-base';
export default CustomizationBase;

View File

@ -0,0 +1,24 @@
import { on, observes } from 'ember-addons/ember-computed-decorators';
import autosize from 'admin/lib/autosize';
export default Ember.TextArea.extend({
@on('didInsertElement')
_startWatching() {
Ember.run.scheduleOnce('afterRender', () => {
this.$().focus();
autosize(this.element);
});
},
@observes('value')
_updateAutosize() {
const evt = document.createEvent('Event');
evt.initEvent('autosize:update', true, false);
this.element.dispatchEvent(evt);
},
@on('willDestroyElement')
_disableAutosize() {
autosize.destroy(this.$());
}
});

View File

@ -1,79 +0,0 @@
import ScreenedIpAddress from 'admin/models/screened-ip-address';
/**
A form to create an IP address that will be blocked or whitelisted.
Example usage:
{{screened-ip-address-form action="recordAdded"}}
where action is a callback on the controller or route that will get called after
the new record is successfully saved. It is called with the new ScreenedIpAddress record
as an argument.
@class ScreenedIpAddressFormComponent
@extends Ember.Component
@namespace Discourse
@module Discourse
**/
const ScreenedIpAddressFormComponent = Ember.Component.extend({
classNames: ['screened-ip-address-form'],
formSubmitted: false,
actionName: 'block',
adminWhitelistEnabled: function() {
return Discourse.SiteSettings.use_admin_ip_whitelist;
}.property(),
actionNames: function() {
if (this.get('adminWhitelistEnabled')) {
return [
{id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')},
{id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')},
{id: 'allow_admin', name: I18n.t('admin.logs.screened_ips.actions.allow_admin')}
];
} else {
return [
{id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')},
{id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')}
];
}
}.property('adminWhitelistEnabled'),
actions: {
submit: function() {
if (!this.get('formSubmitted')) {
var self = this;
this.set('formSubmitted', true);
var screenedIpAddress = ScreenedIpAddress.create({ip_address: this.get('ip_address'), action_name: this.get('actionName')});
screenedIpAddress.save().then(function(result) {
self.set('ip_address', '');
self.set('formSubmitted', false);
self.sendAction('action', ScreenedIpAddress.create(result.screened_ip_address));
Em.run.schedule('afterRender', function() { self.$('.ip-address-input').focus(); });
}, function(e) {
self.set('formSubmitted', false);
var msg;
if (e.responseJSON && e.responseJSON.errors) {
msg = I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')});
} else {
msg = I18n.t("generic_error");
}
bootbox.alert(msg, function() { self.$('.ip-address-input').focus(); });
});
}
}
},
didInsertElement: function() {
var self = this;
this._super();
Em.run.schedule('afterRender', function() {
self.$('.ip-address-input').keydown(function(e) {
if (e.keyCode === 13) { // enter key
self.send('submit');
}
});
});
}
});
export default ScreenedIpAddressFormComponent;

View File

@ -0,0 +1,75 @@
/**
A form to create an IP address that will be blocked or whitelisted.
Example usage:
{{screened-ip-address-form action="recordAdded"}}
where action is a callback on the controller or route that will get called after
the new record is successfully saved. It is called with the new ScreenedIpAddress record
as an argument.
**/
import ScreenedIpAddress from 'admin/models/screened-ip-address';
import computed from 'ember-addons/ember-computed-decorators';
import { on } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNames: ['screened-ip-address-form'],
formSubmitted: false,
actionName: 'block',
@computed
adminWhitelistEnabled() {
return Discourse.SiteSettings.use_admin_ip_whitelist;
},
@computed("adminWhitelistEnabled")
actionNames(adminWhitelistEnabled) {
if (adminWhitelistEnabled) {
return [
{id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')},
{id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')},
{id: 'allow_admin', name: I18n.t('admin.logs.screened_ips.actions.allow_admin')}
];
} else {
return [
{id: 'block', name: I18n.t('admin.logs.screened_ips.actions.block')},
{id: 'do_nothing', name: I18n.t('admin.logs.screened_ips.actions.do_nothing')}
];
}
},
actions: {
submit() {
if (!this.get('formSubmitted')) {
this.set('formSubmitted', true);
const screenedIpAddress = ScreenedIpAddress.create({
ip_address: this.get('ip_address'),
action_name: this.get('actionName')
});
screenedIpAddress.save().then(result => {
this.setProperties({ ip_address: '', formSubmitted: false });
this.sendAction('action', ScreenedIpAddress.create(result.screened_ip_address));
Ember.run.schedule('afterRender', () => this.$('.ip-address-input').focus());
}).catch(e => {
this.set('formSubmitted', false);
const msg = (e.responseJSON && e.responseJSON.errors) ?
I18n.t("generic_error_with_reason", {error: e.responseJSON.errors.join('. ')}) :
I18n.t("generic_error");
bootbox.alert(msg, () => this.$('.ip-address-input').focus());
});
}
}
},
@on("didInsertElement")
_init() {
Ember.run.schedule('afterRender', () => {
this.$('.ip-address-input').keydown(e => {
if (e.keyCode === 13) {
this.send('submit');
}
});
});
}
});

View File

@ -0,0 +1,25 @@
import { on } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNames: ['site-text'],
classNameBindings: ['siteText.overridden'],
@on('didInsertElement')
highlightTerm() {
const term = this.get('term');
if (term) {
this.$('.site-text-id, .site-text-value').highlight(term, {className: 'text-highlight'});
}
this.$('.site-text-value').ellipsis();
},
click() {
this.send('edit');
},
actions: {
edit() {
this.sendAction('editAction', this.get('siteText'));
}
}
});

View File

@ -4,6 +4,15 @@ import { bufferedProperty } from 'discourse/mixins/buffered-content';
export default Ember.Controller.extend(bufferedProperty('emailTemplate'), {
saved: false,
hasMultipleSubjects: function() {
const buffered = this.get('buffered');
if (buffered.getProperties('subject')['subject']) {
return false;
} else {
return buffered.getProperties('id')['id'];
}
}.property("buffered"),
actions: {
saveChanges() {
const buffered = this.get('buffered');

View File

@ -1,14 +1,29 @@
export default Ember.Controller.extend({
saved: false,
import { popupAjaxError } from 'discourse/lib/ajax-error';
import { bufferedProperty } from 'discourse/mixins/buffered-content';
saveDisabled: function() {
return ((!this.get('allow_blank')) && Ember.isEmpty(this.get('model.value')));
}.property('model.iSaving', 'model.value'),
export default Ember.Controller.extend(bufferedProperty('siteText'), {
saved: false,
actions: {
saveChanges() {
const model = this.get('model');
model.save(model.getProperties('value')).then(() => this.set('saved', true));
const buffered = this.get('buffered');
this.get('siteText').save(buffered.getProperties('value')).then(() => {
this.commitBuffer();
this.set('saved', true);
}).catch(popupAjaxError);
},
revertChanges() {
this.set('saved', false);
bootbox.confirm(I18n.t('admin.site_text.revert_confirm'), result => {
if (result) {
this.get('siteText').revert().then(props => {
const buffered = this.get('buffered');
buffered.setProperties(props);
this.commitBuffer();
}).catch(popupAjaxError);
}
});
}
}
});

View File

@ -0,0 +1,51 @@
import { default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend({
_q: null,
searching: false,
siteTexts: null,
preferred: false,
_overridden: null,
queryParams: ['q', 'overridden'],
@computed
overridden: {
set(value) {
if (!value || value === "false") { value = false; }
this._overridden = value;
return value;
},
get() {
return this._overridden;
}
},
@computed
q: {
set(value) {
if (Ember.isEmpty(value)) { value = null; }
this._q = value;
return value;
},
get() {
return this._q;
}
},
_performSearch() {
this.store.find('site-text', this.getProperties('q', 'overridden')).then(results => {
this.set('siteTexts', results);
}).finally(() => this.set('searching', false));
},
actions: {
edit(siteText) {
this.transitionToRoute('adminSiteText.edit', siteText.get('id'));
},
search() {
this.set('searching', true);
Ember.run.debounce(this, this._performSearch, 400);
}
}
});

View File

@ -1 +0,0 @@
export default Ember.ArrayController.extend();

View File

@ -0,0 +1,3 @@
Em.Handlebars.helper('preserve-newlines', str => {
return new Handlebars.SafeString(Discourse.Utilities.escapeExpression(str).replace(/\n/g, "<br>"));
});

View File

@ -0,0 +1,200 @@
const set = (typeof Set === "function") ? new Set() : (function () {
const list = [];
return {
has(key) {
return Boolean(list.indexOf(key) > -1);
},
add(key) {
list.push(key);
},
delete(key) {
list.splice(list.indexOf(key), 1);
},
};
})();
function assign(ta, {setOverflowX = true, setOverflowY = true} = {}) {
if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || set.has(ta)) return;
let heightOffset = null;
let overflowY = null;
let clientWidth = ta.clientWidth;
function init() {
const style = window.getComputedStyle(ta, null);
overflowY = style.overflowY;
if (style.resize === 'vertical') {
ta.style.resize = 'none';
} else if (style.resize === 'both') {
ta.style.resize = 'horizontal';
}
if (style.boxSizing === 'content-box') {
heightOffset = -(parseFloat(style.paddingTop)+parseFloat(style.paddingBottom));
} else {
heightOffset = parseFloat(style.borderTopWidth)+parseFloat(style.borderBottomWidth);
}
// Fix when a textarea is not on document body and heightOffset is Not a Number
if (isNaN(heightOffset)) {
heightOffset = 0;
}
update();
}
function changeOverflow(value) {
{
// Chrome/Safari-specific fix:
// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
// made available by removing the scrollbar. The following forces the necessary text reflow.
const width = ta.style.width;
ta.style.width = '0px';
// Force reflow:
/* jshint ignore:start */
ta.offsetWidth;
/* jshint ignore:end */
ta.style.width = width;
}
overflowY = value;
if (setOverflowY) {
ta.style.overflowY = value;
}
resize();
}
function resize() {
const htmlTop = window.pageYOffset;
const bodyTop = document.body.scrollTop;
const originalHeight = ta.style.height;
ta.style.height = 'auto';
let endHeight = ta.scrollHeight+heightOffset;
if (ta.scrollHeight === 0) {
// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
ta.style.height = originalHeight;
return;
}
ta.style.height = endHeight+'px';
// used to check if an update is actually necessary on window.resize
clientWidth = ta.clientWidth;
// prevents scroll-position jumping
document.documentElement.scrollTop = htmlTop;
document.body.scrollTop = bodyTop;
}
function update() {
const startHeight = ta.style.height;
resize();
const style = window.getComputedStyle(ta, null);
if (style.height !== ta.style.height) {
if (overflowY !== 'visible') {
changeOverflow('visible');
}
} else {
if (overflowY !== 'hidden') {
changeOverflow('hidden');
}
}
if (startHeight !== ta.style.height) {
const evt = document.createEvent('Event');
evt.initEvent('autosize:resized', true, false);
ta.dispatchEvent(evt);
}
}
const pageResize = () => {
if (ta.clientWidth !== clientWidth) {
update();
}
};
const destroy = style => {
window.removeEventListener('resize', pageResize, false);
ta.removeEventListener('input', update, false);
ta.removeEventListener('keyup', update, false);
ta.removeEventListener('autosize:destroy', destroy, false);
ta.removeEventListener('autosize:update', update, false);
set.delete(ta);
Object.keys(style).forEach(key => {
ta.style[key] = style[key];
});
}.bind(ta, {
height: ta.style.height,
resize: ta.style.resize,
overflowY: ta.style.overflowY,
overflowX: ta.style.overflowX,
wordWrap: ta.style.wordWrap,
});
ta.addEventListener('autosize:destroy', destroy, false);
// IE9 does not fire onpropertychange or oninput for deletions,
// so binding to onkeyup to catch most of those events.
// There is no way that I know of to detect something like 'cut' in IE9.
if ('onpropertychange' in ta && 'oninput' in ta) {
ta.addEventListener('keyup', update, false);
}
window.addEventListener('resize', pageResize, false);
ta.addEventListener('input', update, false);
ta.addEventListener('autosize:update', update, false);
set.add(ta);
if (setOverflowX) {
ta.style.overflowX = 'hidden';
ta.style.wordWrap = 'break-word';
}
init();
}
function exportDestroy(ta) {
if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
const evt = document.createEvent('Event');
evt.initEvent('autosize:destroy', true, false);
ta.dispatchEvent(evt);
}
function exportUpdate(ta) {
if (!(ta && ta.nodeName && ta.nodeName === 'TEXTAREA')) return;
const evt = document.createEvent('Event');
evt.initEvent('autosize:update', true, false);
ta.dispatchEvent(evt);
}
let autosize = (el, options) => {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], x => assign(x, options));
}
return el;
};
autosize.destroy = el => {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], exportDestroy);
}
return el;
};
autosize.update = el => {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], exportUpdate);
}
return el;
};
export default autosize;

View File

@ -147,7 +147,7 @@ Report.reopenClass({
if (maxY > 0) {
json.report.data.forEach(row => row.percentage = Math.round((row.y / maxY) * 100));
}
const model = Discourse.Report.create({ type: type });
const model = Report.create({ type: type });
model.setProperties(json.report);
return model;
});

View File

@ -1,2 +0,0 @@
import RestModel from 'discourse/models/rest';
export default RestModel.extend();

View File

@ -1,8 +1,10 @@
import RestModel from 'discourse/models/rest';
const { getProperties } = Ember;
export default RestModel.extend({
markdown: Em.computed.equal('format', 'markdown'),
plainText: Em.computed.equal('format', 'plain'),
html: Em.computed.equal('format', 'html'),
css: Em.computed.equal('format', 'css'),
revert() {
return Discourse.ajax(`/admin/customize/site_texts/${this.get('id')}`, {
method: 'DELETE'
}).then(result => getProperties(result.site_text, 'value', 'can_revert'));
}
});

View File

@ -6,8 +6,8 @@ export default Ember.Route.extend({
return all.findProperty('id', params.id);
},
setupController(controller, model) {
controller.set('emailTemplate', model);
setupController(controller, emailTemplate) {
controller.setProperties({ emailTemplate, saved: false });
scrollTop();
}
});

View File

@ -8,7 +8,8 @@
**/
export default Discourse.Route.extend({
model: function(params) {
return Discourse.Report.find(params.type);
const Report = require('admin/models/report').default;
return Report.find(params.type);
},
setupController: function(controller, model) {

View File

@ -22,8 +22,9 @@ export default {
});
this.resource('adminSiteText', { path: '/site_texts' }, function() {
this.route('edit', {path: '/:text_type'});
this.route('edit', { path: '/:id' });
});
this.resource('adminUserFields', { path: '/user_fields' });
this.resource('adminEmojis', { path: '/emojis' });
this.resource('adminPermalinks', { path: '/permalinks' });

View File

@ -1,5 +1,9 @@
export default Discourse.Route.extend({
export default Ember.Route.extend({
model(params) {
return this.store.find('site-text', params.text_type);
return this.store.find('site-text', params.id);
},
setupController(controller, siteText) {
controller.setProperties({ siteText, saved: false });
}
});

View File

@ -0,0 +1,14 @@
export default Ember.Route.extend({
queryParams: {
q: { replace: true },
overridden: { replace: true }
},
model(params) {
return this.store.find('site-text', Ember.getProperties(params, 'q', 'overridden'));
},
setupController(controller, model) {
controller.set('siteTexts', model);
}
});

View File

@ -1,5 +0,0 @@
export default Discourse.Route.extend({
model() {
return this.store.findAll('site-text-type');
}
});

View File

@ -1,5 +1,7 @@
{{d-button action="saveChanges" disabled=buttonDisabled label=savingText class="btn-primary"}}
{{d-button action="saveChanges" disabled=buttonDisabled label=savingText class="btn-primary save-changes"}}
{{yield}}
<div class='save-messages'>
{{#if saved}}{{i18n 'saved'}}{{/if}}
{{#if saved}}
<div class='saved'>{{i18n 'saved'}}</div>
{{/if}}
</div>

View File

@ -1,4 +1,4 @@
<b>{{i18n 'admin.logs.screened_ips.form.label'}}</b>
{{text-field value=ip_address disabled=formSubmitted class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.ip_address" autocorrect="off" autocapitalize="off"}}
{{combo-box content=actionNames value=actionName}}
<button class="btn" {{action "submit" target="view"}} {{bind-attr disabled="formSubmitted"}}>{{i18n 'admin.logs.screened_ips.form.add'}}</button>
{{d-button action="submit" disabled=formSubmitted label="admin.logs.screened_ips.form.add"}}

View File

@ -0,0 +1,5 @@
{{d-button label="admin.site_text.edit" class='edit' action="edit"}}
<h3 class='site-text-id'>{{siteText.id}}</h3>
<div class='site-text-value'>{{siteText.value}}</div>
<div class='clearfix'></div>

View File

@ -1,13 +1,14 @@
<div class='email-template'>
<label>
{{i18n "admin.customize.email_templates.subject"}}
<label>{{i18n "admin.customize.email_templates.subject"}}</label>
{{#if hasMultipleSubjects}}
<h3>{{#link-to 'adminSiteText' (query-params q=hasMultipleSubjects)}}{{i18n "admin.customize.email_templates.multiple_subjects"}}{{/link-to}}</h3>
{{else}}
{{input value=buffered.subject}}
</label>
{{/if}}
<br>
<label>
{{i18n "admin.customize.email_templates.body"}}
{{d-editor value=buffered.body}}
</label>
<label>{{i18n "admin.customize.email_templates.body"}}</label>
{{d-editor value=buffered.body}}
{{#save-controls model=emailTemplate action="saveChanges" saved=saved}}
{{#if emailTemplate.can_revert}}

View File

@ -92,6 +92,13 @@
<label for="grant_trust_level">{{i18n 'groups.trust_levels.title'}}</label>
{{combo-box name="grant_trust_level" valueAttribute="value" value=model.grant_trust_level content=trustLevelOptions}}
</div>
{{#if siteSettings.email_in}}
<div>
<label for="incoming_email">{{i18n 'admin.groups.incoming_email'}}</label>
{{text-field name="incoming_email" value=model.incoming_email placeholderKey="admin.groups.incoming_email_placeholder"}}
</div>
{{/if}}
{{/unless}}
<div class='buttons'>

View File

@ -1,11 +1,14 @@
<p>{{i18n 'admin.logs.screened_ips.description'}}</p>
<div class="pull-right">
{{text-field value=filter class="ip-address-input" placeholderKey="admin.logs.screened_ips.form.filter" autocorrect="off" autocapitalize="off"}}
<button class="btn" {{action "rollUp"}} title="{{i18n 'admin.logs.screened_ips.roll_up.title'}}">{{i18n 'admin.logs.screened_ips.roll_up.text'}}</button>
<button class="btn" {{action "exportScreenedIpList"}} title="{{i18n 'admin.export_csv.button_title.screened_ip'}}">{{fa-icon "download"}}{{i18n 'admin.export_csv.button_text'}}</button>
{{d-button action="rollUp" title="admin.logs.screened_ips.roll_up.title" label="admin.logs.screened_ips.roll_up.text"}}
{{d-button action="exportScreenedIpList" icon="download" title="admin.export_csv.button_title.screened_ip" label="admin.export_csv.button_text"}}
</div>
<div>
{{screened-ip-address-form action="recordAdded"}}
</div>
{{screened-ip-address-form action="recordAdded"}}
<br/>
{{#conditional-loading-spinner condition=loading}}
{{#if model.length}}

View File

@ -19,7 +19,7 @@
{{#link-to 'adminSiteSettingsCategory' category.nameKey class=category.nameKey}}
{{category.name}}
{{#if filtered}}
<span class="count">({{category.count}})</span>
{{#if category.count}}<span class="count">({{category.count}})</span>{{/if}}
{{/if}}
{{/link-to}}
{{/link-to}}

View File

@ -1,17 +1,20 @@
<h3>{{model.title}}</h3>
<p class='description'>{{model.description}}</p>
<div class='edit-site-text'>
{{#if model.markdown}}
{{d-editor value=model.value}}
{{/if}}
{{#if model.plainText}}
{{textarea value=model.value class="plain"}}
{{/if}}
{{#if model.html}}
{{ace-editor content=model.value mode="html"}}
{{/if}}
{{#if model.css}}
{{ace-editor content=model.value mode="css"}}
{{/if}}
<div class='title'>
<h3>{{siteText.id}}</h3>
</div>
{{save-controls model=model action="saveChanges" saveDisabled=saveDisabled saved=saved}}
{{expanding-text-area value=buffered.value rows="1" class="site-text-value"}}
{{#save-controls model=siteText action="saveChanges" saved=saved}}
{{#if siteText.can_revert}}
{{d-button action="revertChanges" label="admin.site_text.revert" class="revert-site-text"}}
{{/if}}
{{/save-controls}}
{{#link-to 'adminSiteText.index' class="go-back"}}
{{fa-icon 'arrow-left'}}
{{i18n 'admin.site_text.go_back'}}
{{/link-to}}
</div>

View File

@ -1 +1,23 @@
<p>{{i18n 'admin.site_text.none'}}</p>
<div class='search-area'>
<p>{{i18n "admin.site_text.description"}}</p>
{{text-field value=q
placeholderKey="admin.site_text.search"
class="no-blur site-text-search"
autofocus="true"
key-up="search"}}
<div class='extra-options'>
{{d-checkbox label="admin.site_text.show_overriden" checked=overridden change="search"}}
</div>
</div>
{{#conditional-loading-spinner condition=searching}}
{{#if siteTexts.extras.recommended}}
<p><b>{{i18n "admin.site_text.recommended"}}</b></p>
{{/if}}
{{#each siteTexts as |siteText|}}
{{site-text-summary siteText=siteText editAction="edit" term=q}}
{{/each}}
{{/conditional-loading-spinner}}

View File

@ -1,15 +1,3 @@
<div class='row'>
<div class='content-list span6'>
<ul>
{{#each c in model}}
<li>
{{#link-to 'adminSiteText.edit' c.text_type}}{{c.title}}{{/link-to}}
</li>
{{/each}}
</ul>
</div>
<div class='content-editor'>
{{outlet}}
</div>
<div class='row site-texts'>
{{outlet}}
</div>

View File

@ -434,26 +434,26 @@
<section class='details'>
<h1>{{i18n 'admin.user.sso.title'}}</h1>
{{#with model.single_sign_on_record}}
{{#with model.single_sign_on_record as |sso|}}
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_id'}}</div>
<div class='value'>{{external_id}}</div>
<div class='value'>{{sso.external_id}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_username'}}</div>
<div class='value'>{{external_username}}</div>
<div class='value'>{{sso.external_username}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_name'}}</div>
<div class='value'>{{external_name}}</div>
<div class='value'>{{sso.external_name}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_email'}}</div>
<div class='value'>{{external_email}}</div>
<div class='value'>{{sso.external_email}}</div>
</div>
<div class='display-row'>
<div class='field'>{{i18n 'admin.user.sso.external_avatar_url'}}</div>
<div class='value'>{{external_avatar_url}}</div>
<div class='value'>{{sso.external_avatar_url}}</div>
</div>
{{/with}}
</section>

View File

@ -1,4 +1,5 @@
import StringBuffer from 'discourse/mixins/string-buffer';
import UserAction from "discourse/models/user-action";
export default Ember.Component.extend(StringBuffer, {
tagName: 'li',
@ -27,9 +28,9 @@ export default Ember.Component.extend(StringBuffer, {
typeKey: function() {
const actionType = this.get('content.action_type');
if (actionType === Discourse.UserAction.TYPES.messages_received) { return ""; }
if (actionType === UserAction.TYPES.messages_received) { return ""; }
const result = Discourse.UserAction.TYPES_INVERTED[actionType];
const result = UserAction.TYPES_INVERTED[actionType];
if (!result) { return ""; }
// We like our URLS to have hyphens, not underscores
@ -55,11 +56,11 @@ export default Ember.Component.extend(StringBuffer, {
icon: function() {
switch(parseInt(this.get('content.action_type'), 10)) {
case Discourse.UserAction.TYPES.likes_received: return "heart";
case Discourse.UserAction.TYPES.bookmarks: return "bookmark";
case Discourse.UserAction.TYPES.edits: return "pencil";
case Discourse.UserAction.TYPES.replies: return "reply";
case Discourse.UserAction.TYPES.mentions: return "at";
case UserAction.TYPES.likes_received: return "heart";
case UserAction.TYPES.bookmarks: return "bookmark";
case UserAction.TYPES.edits: return "pencil";
case UserAction.TYPES.replies: return "reply";
case UserAction.TYPES.mentions: return "at";
}
}.property("content.action_type")
});

View File

@ -1,12 +0,0 @@
import { on } from "ember-addons/ember-computed-decorators";
export default Ember.TextField.extend({
@on("didInsertElement")
becomeFocused() {
const input = this.get("element");
input.focus();
input.selectionStart = input.selectionEnd = input.value.length;
}
});

View File

@ -2,6 +2,7 @@ import ComboboxView from 'discourse/components/combo-box';
import { categoryBadgeHTML } from 'discourse/helpers/category-link';
import computed from 'ember-addons/ember-computed-decorators';
import { observes, on } from 'ember-addons/ember-computed-decorators';
import PermissionType from 'discourse/models/permission-type';
export default ComboboxView.extend({
classNames: ['combobox category-combobox'],
@ -21,7 +22,8 @@ export default ComboboxView.extend({
return categories.filter(c => {
if (scopedCategoryId && c.get('id') !== scopedCategoryId && c.get('parent_category_id') !== scopedCategoryId) { return false; }
if (c.get('isUncategorizedCategory')) { return false; }
return c.get('permission') === Discourse.PermissionType.FULL;
if (c.get('contains_messages')) { return false; }
return c.get('permission') === PermissionType.FULL;
});
},

View File

@ -1,19 +1,17 @@
import { iconHTML } from 'discourse/helpers/fa-icon';
export default Em.Component.extend({
tagName: 'h3',
render: function(buffer) {
var category = this.get('category'),
logoUrl = category.get('logo_url'),
categoryUrl = Discourse.getURL('/c/') + Discourse.Category.slugFor(category),
categoryName = Handlebars.Utils.escapeExpression(category.get('name'));
render(buffer) {
const category = this.get('category');
const categoryUrl = Discourse.getURL('/c/') + Discourse.Category.slugFor(category);
const categoryName = Handlebars.Utils.escapeExpression(category.get('name'));
if (category.get('read_restricted')) { buffer.push("<i class='fa fa-lock'></i>"); }
if (category.get('read_restricted')) { buffer.push(iconHTML('lock')); }
buffer.push("<a href='" + categoryUrl + "'>");
buffer.push("<span class='category-name'>" + categoryName + "</span>");
if (!Em.isEmpty(logoUrl)) { buffer.push("<img src='" + logoUrl + "' class='category-logo'>"); }
buffer.push("</a>");
buffer.push(`<a href='${categoryUrl}'>`);
buffer.push(`<span class='category-name'>${categoryName}</span>`);
buffer.push(`</a>`);
}
});

View File

@ -72,17 +72,16 @@ export default Ember.Component.extend({
}
const $elem = this.$();
const minimumResultsForSearch = this.capabilities.touch ? -1 : 5;
const minimumResultsForSearch = this.capabilities.isIOS ? -1 : 5;
$elem.select2({formatResult: this.comboTemplate, minimumResultsForSearch, width: 'resolve'});
const castInteger = this.get('castInteger');
const self = this;
$elem.on("change", function (e) {
$elem.on("change", e => {
let val = $(e.target).val();
if (val && val.length && castInteger) {
val = parseInt(val, 10);
}
self.set('value', val);
this.set('value', val);
});
$elem.trigger('change');
}.on('didInsertElement'),

View File

@ -53,13 +53,13 @@ export default Ember.Component.extend({
template,
dataSource: term => userSearch({ term, topicId, includeGroups: true }),
key: "@",
transformComplete: v => v.username || v.usernames.join(", @")
transformComplete: v => v.username || v.name
});
$input.on('scroll', () => Ember.run.throttle(this, this._syncEditorAndPreviewScroll, 20));
// Focus on the body unless we have a title
if (!this.get('composer.canEditTitle') && !this.capabilities.touch) {
if (!this.get('composer.canEditTitle') && !this.capabilities.isIOS) {
this.$('.d-editor-input').putCursorAtEnd();
}
@ -114,6 +114,25 @@ export default Ember.Component.extend({
_renderUnseen: function($preview, unseen) {
fetchUnseenMentions($preview, unseen, this.siteSettings).then(() => {
linkSeenMentions($preview, this.siteSettings);
this._warnMentionedGroups($preview);
});
},
_warnMentionedGroups($preview) {
Ember.run.scheduleOnce('afterRender', () => {
this._warnedMentions = this._warnedMentions || [];
var found = [];
$preview.find('.mention-group.notify').each((idx,e) => {
const $e = $(e);
var name = $e.data('name');
found.push(name);
if (this._warnedMentions.indexOf(name) === -1){
this._warnedMentions.push(name);
this.sendAction('groupsMentioned', [{name: name, user_count: $e.data('mentionable-user-count')}]);
}
});
this._warnedMentions = found;
});
},
@ -370,6 +389,8 @@ export default Ember.Component.extend({
Ember.run.debounce(this, this._renderUnseen, $preview, unseen, 500);
}
this._warnMentionedGroups($preview);
const post = this.get('composer.post');
let refresh = false;

View File

@ -5,7 +5,7 @@ export default Ember.Component.extend({
@on('didInsertElement')
_focusOnTitle() {
if (!this.capabilities.touch) {
if (!this.capabilities.isIOS) {
this.$('input').putCursorAtEnd();
}
},

View File

@ -0,0 +1,18 @@
import { on } from "ember-addons/ember-computed-decorators";
export default Ember.Component.extend({
tagName: 'label',
@on('didInsertElement')
_watchChanges() {
// In Ember 13.3 we can use action on the checkbox `{{input}}` but not in 1.11
this.$('input').on('click.d-checkbox', () => {
Ember.run.scheduleOnce('afterRender', () => this.sendAction('change'));
});
},
@on('willDestroyElement')
_stopWatching() {
this.$('input').off('click.d-checkbox');
}
});

View File

@ -29,9 +29,11 @@ export default Ember.Component.extend({
if (key.keyCode === 27) {
this.send('cancel');
return false;
}
if (key.keyCode === 13) {
this.send('ok');
return false;
}
});
},

View File

@ -318,6 +318,9 @@ export default Ember.Component.extend({
_selectText(from, length) {
Ember.run.scheduleOnce('afterRender', () => {
const textarea = this.$('textarea.d-editor-input')[0];
if (!this.capabilities.isIOS) {
textarea.focus();
}
textarea.selectionStart = from;
textarea.selectionEnd = textarea.selectionStart + length;
});
@ -412,7 +415,7 @@ export default Ember.Component.extend({
const insert = `${sel.pre}${text}`;
this.set('value', `${insert}${sel.post}`);
this._selectText(insert.length, 0);
Ember.run.once("afterRender", () => { $("textarea.d-editor-input").focus(); } );
Ember.run.scheduleOnce("afterRender", () => this.$("textarea.d-editor-input").focus());
},
actions: {

View File

@ -1,4 +1,5 @@
import { buildCategoryPanel } from 'discourse/components/edit-category-panel';
import PermissionType from 'discourse/models/permission-type';
export default buildCategoryPanel('security', {
editingPermissions: false,
@ -16,7 +17,7 @@ export default buildCategoryPanel('security', {
if (!this.get('category.is_special')) {
this.get('category').addPermission({
group_name: group + "",
permission: Discourse.PermissionType.create({id})
permission: PermissionType.create({id})
});
}
},

View File

@ -0,0 +1,11 @@
import NotificationsButton from 'discourse/components/notifications-button';
export default NotificationsButton.extend({
classNames: ['notification-options', 'group-notification-menu'],
notificationLevel: Em.computed.alias('group.notification_level'),
i18nPrefix: 'groups.notifications',
clicked(id) {
this.get('group').setNotification(id);
}
});

View File

@ -100,7 +100,7 @@ export default Ember.Component.extend({
this._watchSizeChanges();
// iOS does not handle scroll events well
if (!this.capabilities.touch) {
if (!this.capabilities.isIOS) {
$(window).on('scroll.discourse-menu-panel', () => this.performLayout());
}
} else {

View File

@ -1,7 +1,7 @@
import { on } from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
classNameBindings: ["visible::hidden", ":popup-menu"],
classNameBindings: ["visible::hidden", ":popup-menu", "extraClasses"],
@on('didInsertElement')
_setup() {

View File

@ -0,0 +1,60 @@
import Combobox from 'discourse/components/combo-box';
import { on, observes } from 'ember-addons/ember-computed-decorators';
export default Combobox.extend({
none: "topic.controls",
@on('init')
_createContent() {
const content = [];
const topic = this.get('topic');
const details = topic.get('details');
if (details.get('can_invite_to')) {
content.push({ id: 'invite', name: I18n.t('topic.invite_reply.title') });
}
if (topic.get('bookmarked')) {
content.push({ id: 'bookmark', name: I18n.t('bookmarked.clear_bookmarks') });
} else {
content.push({ id: 'bookmark', name: I18n.t('bookmarked.title') });
}
content.push({ id: 'share', name: I18n.t('topic.share.title') });
if (details.get('can_flag_topic')) {
content.push({ id: 'flag', name: I18n.t('topic.flag_topic.title') });
}
this.set('content', content);
},
@observes('value')
_valueChanged() {
const value = this.get('value');
const controller = this.get('parentView.controller');
const topic = this.get('topic');
const refresh = () => {
this._createContent();
this.set('value', null);
};
switch(value) {
case 'invite':
controller.send('showInvite');
refresh();
break;
case 'bookmark':
topic.toggleBookmark().then(() => refresh());
break;
case 'share':
this.appEvents.trigger('share:url', topic.get('shareUrl'), $('#topic-footer-buttons'));
refresh();
break;
case 'flag':
controller.send('showFlagTopic', topic);
refresh();
break;
}
}
});

View File

@ -6,7 +6,9 @@ export default TextField.extend({
_initializeAutocomplete: function() {
var self = this,
selected = [],
groups = [],
currentUser = this.currentUser,
includeMentionableGroups = this.get('includeMentionableGroups') === 'true',
includeGroups = this.get('includeGroups') === 'true',
allowedUsers = this.get('allowedUsers') === 'true';
@ -24,18 +26,22 @@ export default TextField.extend({
allowAny: this.get('allowAny'),
dataSource: function(term) {
return userSearch({
var results = userSearch({
term: term.replace(/[^a-zA-Z0-9_\-\.]/, ''),
topicId: self.get('topicId'),
exclude: excludedUsernames(),
includeGroups,
allowedUsers
allowedUsers,
includeMentionableGroups
});
return results;
},
transformComplete: function(v) {
if (v.username) {
return v.username;
if (v.username || v.name) {
if (!v.username) { groups.push(v.name); }
return v.username || v.name;
} else {
var excludes = excludedUsernames();
return v.usernames.filter(function(item){
@ -45,10 +51,14 @@ export default TextField.extend({
},
onChangeItems: function(items) {
var hasGroups = false;
items = items.map(function(i) {
if (groups.indexOf(i) > -1) { hasGroups = true; }
return i.username ? i.username : i;
});
self.set('usernames', items.join(","));
self.set('hasGroups', hasGroups);
selected = items;
},

View File

@ -48,6 +48,23 @@ export default Ember.ArrayController.extend({
this.get('queuedForTyping').forEach(msg => this.send("popup", msg));
},
groupsMentioned(groups) {
// reset existing messages, this should always win it is critical
this.reset();
groups.forEach(group => {
const msg = I18n.t('composer.group_mentioned', {
group: "@" + group.name,
count: group.user_count,
group_link: Discourse.getURL(`/group/${group.name}/members`)
});
this.send("popup",
Em.Object.create({
templateName: 'composer/group-mentioned',
body: msg})
);
});
},
// Figure out if there are any messages that should be displayed above the composer.
queryFor(composer) {
if (this.get('checkedMessages')) { return; }

View File

@ -75,9 +75,10 @@ export default Ember.Controller.extend({
if (!Discourse.User.currentProp('staff')) { return false; }
var usernames = this.get('model.targetUsernames');
var hasTargetGroups = this.get('model.hasTargetGroups');
// We need exactly one user to issue a warning
if (Ember.isEmpty(usernames) || usernames.split(',').length !== 1) {
if (Ember.isEmpty(usernames) || usernames.split(',').length !== 1 || hasTargetGroups) {
return false;
}
return this.get('model.creatingPrivateMessage');
@ -114,7 +115,7 @@ export default Ember.Controller.extend({
// If there is no current post, use the first post id from the stream
if (!postId && postStream) {
postId = postStream.get('firstPostId');
postId = postStream.get('stream.firstObject');
}
// If we're editing a post, fetch the reply when importing a quote
@ -170,6 +171,12 @@ export default Ember.Controller.extend({
}
},
groupsMentioned(groups) {
if (!this.get('model.creatingPrivateMessage') && !this.get('model.topic.isPrivateMessage')) {
this.get('controllers.composer-messages').groupsMentioned(groups);
}
}
},
categories: function() {

View File

@ -42,7 +42,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
},
submitDisabled: function() {
if (!this.get('passwordRequired')) return false; // 3rd party auth
if (!this.get('emailValidation.failed') && !this.get('passwordRequired')) return false; // 3rd party auth
if (this.get('formSubmitted')) return true;
if (this.get('nameValidation.failed')) return true;
if (this.get('emailValidation.failed')) return true;

View File

@ -12,7 +12,6 @@ export default DiscoveryController.extend({
actions: {
refresh() {
// Don't refresh if we're still loading
if (this.get('controllers.discovery.loading')) { return; }
@ -21,9 +20,10 @@ export default DiscoveryController.extend({
// Lesson learned: Don't call `loading` yourself.
this.set('controllers.discovery.loading', true);
const CategoryList = require('discourse/models/category-list').default;
const parentCategory = this.get('model.parentCategory');
const promise = parentCategory ? Discourse.CategoryList.listForParent(this.store, parentCategory) :
Discourse.CategoryList.list(this.store);
const promise = parentCategory ? CategoryList.listForParent(this.store, parentCategory) :
CategoryList.list(this.store);
const self = this;
promise.then(function(list) {
@ -38,7 +38,7 @@ export default DiscoveryController.extend({
}.property(),
latestTopicOnly: function() {
return this.get('model.categories').find(function(c) { return c.get('featuredTopics.length') > 1; }) === undefined;
return this.get('model.categories').find(c => c.get('featuredTopics.length') > 1) === undefined;
}.property('model.categories.@each.featuredTopics.length')
});

View File

@ -5,6 +5,8 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
export default Ember.Controller.extend(ModalFunctionality, {
auto_close_valid: true,
auto_close_invalid: Em.computed.not('auto_close_valid'),
disable_submit: Em.computed.or('auto_close_invalid', 'loading'),
loading: false,
@observes("model.details.auto_close_at", "model.details.auto_close_hours")
setAutoCloseTime() {
@ -29,7 +31,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
setAutoClose(time) {
const self = this;
this.send('hideModal');
this.set('loading', true);
Discourse.ajax({
url: `/t/${this.get('model.id')}/autoclose`,
type: 'PUT',
@ -40,16 +42,34 @@ export default Ember.Controller.extend(ModalFunctionality, {
timezone_offset: (new Date().getTimezoneOffset())
}
}).then(result => {
self.set('loading', false);
if (result.success) {
this.send('closeModal');
this.set('model.details.auto_close_at', result.auto_close_at);
this.set('model.details.auto_close_hours', result.auto_close_hours);
} else {
bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('reopenModal'); } );
bootbox.alert(I18n.t('composer.auto_close.error'));
}
}).catch(() => {
bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('reopenModal'); } );
// TODO - incorrectly responds to network errors as bad input
bootbox.alert(I18n.t('composer.auto_close.error'));
self.set('loading', false);
});
}
},
willCloseImmediately: function() {
if (!this.get('model.details.auto_close_based_on_last_post')) {
return false;
}
let closeDate = new Date(this.get('model.last_posted_at'));
closeDate.setHours(closeDate.getHours() + this.get('model.auto_close_time'));
return closeDate < new Date();
}.property('model.details.auto_close_based_on_last_post', 'model.auto_close_time', 'model.last_posted_at'),
willCloseI18n: function() {
if (this.get('model.details.auto_close_based_on_last_post')) {
return I18n.t('topic.auto_close_immediate', {hours: this.get('model.auto_close_time')});
}
}.property('model.details.auto_close_based_on_last_post', 'model.auto_close_time')
});

View File

@ -1,4 +1,5 @@
import ModalFunctionality from 'discourse/mixins/modal-functionality';
import ActionSummary from 'discourse/models/action-summary';
import { MAX_MESSAGE_LENGTH } from 'discourse/models/post-action-type';
export default Ember.Controller.extend(ModalFunctionality, {
@ -33,7 +34,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
_.each(this.get("model.actions_summary"),function(a) {
a.flagTopic = self.get('model');
a.actionType = self.site.topicFlagTypeById(a.id);
const actionSummary = Discourse.ActionSummary.create(a);
const actionSummary = ActionSummary.create(a);
lookup.set(a.actionType.get('name_key'), actionSummary);
});
this.set('topicActionByName', lookup);

View File

@ -4,7 +4,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
// You need a value in the field to submit it.
submitDisabled: function() {
return Ember.isEmpty(this.get('accountEmailOrUsername')) || this.get('disabled');
return Ember.isEmpty(this.get('accountEmailOrUsername').trim()) || this.get('disabled');
}.property('accountEmailOrUsername', 'disabled'),
actions: {
@ -43,7 +43,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
};
Discourse.ajax('/session/forgot_password', {
data: { login: this.get('accountEmailOrUsername') },
data: { login: this.get('accountEmailOrUsername').trim() },
type: 'POST'
}).then(success, fail).finally(function(){
setTimeout(function(){

View File

@ -1,9 +1,39 @@
import { default as computed, observes } from 'ember-addons/ember-computed-decorators';
var Tab = Em.Object.extend({
@computed('name')
location(name) {
return 'group.' + name;
}
});
export default Ember.Controller.extend({
counts: null,
showing: null,
showing: 'posts',
// It would be nice if bootstrap marked action lists as selected when their links
// were 'active' not the `li` tags.
showingIndex: Em.computed.equal('showing', 'index'),
showingMembers: Em.computed.equal('showing', 'members')
@observes('counts')
countsChanged() {
const counts = this.get('counts');
this.get('tabs').forEach(tab => {
tab.set('count', counts.get(tab.get('name')));
});
},
@observes('showing')
showingChanged() {
const showing = this.get('showing');
this.get('tabs').forEach(tab => {
tab.set('active', showing === tab.get('name'));
});
},
tabs: [
Tab.create({ name: 'posts', active: true, 'location': 'group.index' }),
Tab.create({ name: 'topics' }),
Tab.create({ name: 'mentions' }),
Tab.create({ name: 'members' }),
Tab.create({ name: 'messages' }),
]
});

View File

@ -1,10 +1,5 @@
/**
Handles displaying posts within a group
@class GroupIndexController
@extends Ember.ArrayController
@namespace Discourse
@module Discourse
**/
export default Ember.ArrayController.extend({
needs: ['group'],
@ -21,7 +16,8 @@ export default Ember.ArrayController.extend({
var lastPostId = posts[posts.length-1].get('id'),
group = this.get('controllers.group.model');
group.findPosts({beforePostId: lastPostId}).then(function(newPosts) {
var opts = {beforePostId: lastPostId, type: this.get('type')};
group.findPosts(opts).then(function(newPosts) {
posts.addObjects(newPosts);
self.set('loading', false);
});

View File

@ -1,20 +1,21 @@
import { popupAjaxError } from 'discourse/lib/ajax-error';
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Controller.extend({
loading: false,
limit: null,
offset: null,
isOwner: function() {
@computed('model.owners.@each')
isOwner(owners) {
if (this.get('currentUser.admin')) {
return true;
}
const owners = this.get('model.owners');
const currentUserId = this.get('currentUser.id');
if (currentUserId) {
return !!owners.findBy('id', currentUserId);
}
}.property('model.owners.@each'),
},
actions: {
removeMember(user) {

View File

@ -0,0 +1,3 @@
import IndexController from 'discourse/controllers/group/index';
export default IndexController.extend({type: 'mentions'});

View File

@ -0,0 +1,3 @@
import IndexController from 'discourse/controllers/group/index';
export default IndexController.extend({type: 'topics'});

View File

@ -81,7 +81,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
const ssoDestinationUrl = $.cookie('sso_destination_url');
$hidden_login_form.find('input[name=username]').val(self.get('loginName'));
$hidden_login_form.find('input[name=password]').val(self.get('loginPassword'));
if (ssoDestinationUrl) {
$.cookie('sso_destination_url', null);
window.location.assign(ssoDestinationUrl);
@ -203,10 +203,14 @@ export default Ember.Controller.extend(ModalFunctionality, {
// Reload the page if we're authenticated
if (options.authenticated) {
const destinationUrl = $.cookie('destination_url');
const shouldRedirectToUrl = self.session.get("shouldRedirectToUrl");
if (self.get('loginRequired') && destinationUrl) {
// redirect client to the original URL
$.cookie('destination_url', null);
window.location.href = destinationUrl;
} else if (shouldRedirectToUrl) {
self.session.set("shouldRedirectToUrl", null);
window.location.href = shouldRedirectToUrl;
} else if (window.location.pathname === Discourse.getURL('/login')) {
window.location.pathname = Discourse.getURL('/');
} else {

View File

@ -34,6 +34,17 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
}
}.observes('model.title', 'category'),
@computed('model.postStream.posts')
postsToRender() {
return this.capabilities.isAndroid ? this.get('model.postStream.posts')
: this.get('model.postStream.postsWithPlaceholders');
},
@computed('model.postStream.loadingFilter')
androidLoading(loading) {
return this.capabilities.isAndroid && loading;
},
@computed('model.postStream.summary')
show_deleted: {
set(value) {
@ -78,6 +89,13 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
this.set('selectedReplies', []);
}.on('init'),
@computed("model.isPrivateMessage", "model.category_id")
showCategoryChooser(isPrivateMessage, categoryId) {
const category = Discourse.Category.findById(categoryId);
const containsMessages = category && category.get("contains_messages");
return !isPrivateMessage && !containsMessages;
},
actions: {
showTopicAdminMenu() {
this.set('adminMenuVisible', true);
@ -141,6 +159,9 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
if (post.get('post_number') === 1) {
this.deleteTopic();
return;
} else if (!post.can_delete) {
// check if current user can delete post
return false;
}
const user = Discourse.User.current(),
@ -182,6 +203,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
return bootbox.alert(I18n.t('post.controls.edit_anonymous'));
}
// check if current user can edit post
if (!post.can_edit) {
return false;
}
const composer = this.get('controllers.composer'),
composerModel = composer.get('model'),
opts = {
@ -394,7 +420,11 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
draftKey: Composer.REPLY_AS_NEW_TOPIC_KEY,
categoryId: this.get('category.id')
}).then(() => {
return Em.isEmpty(quotedText) ? Discourse.Post.loadQuote(post.get('id')) : quotedText;
if (Em.isEmpty(quotedText)) {
return Discourse.Post.loadQuote(post.get('id'));
} else {
composerController.get('model').appendText(quotedText);
}
}).then(q => {
const postUrl = `${location.protocol}//${location.host}${post.get('url')}`;
const postLink = `[${Handlebars.escapeExpression(self.get('model.title'))}](${postUrl})`;
@ -661,8 +691,8 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
topVisibleChanged(post) {
if (!post) { return; }
const postStream = this.get('model.postStream'),
firstLoadedPost = postStream.get('firstLoadedPost');
const postStream = this.get('model.postStream');
const firstLoadedPost = postStream.get('posts.firstObject');
this.set('model.currentPost', post.get('post_number'));
@ -673,15 +703,17 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
// trigger a scroll after a promise resolves in a controller? We need
// to do this to preserve upwards infinte scrolling.
const $body = $('body');
let $elem = $('#post-cloak-' + post.get('post_number'));
const distToElement = $body.scrollTop() - $elem.position().top;
const elemId = `#post_${post.get('post_number')}`;
const $elem = $(elemId).closest('.post-cloak');
const elemPos = $elem.position();
const distToElement = elemPos ? $body.scrollTop() - elemPos.top : 0;
postStream.prependMore().then(function() {
Em.run.next(function () {
$elem = $('#post-cloak-' + post.get('post_number'));
const $refreshedElem = $(elemId).closest('.post-cloak');
// Quickly going back might mean the element is destroyed
const position = $elem.position();
const position = $refreshedElem.position();
if (position && position.top) {
$('html, body').scrollTop(position.top + distToElement);
}
@ -699,8 +731,8 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
bottomVisibleChanged(post) {
if (!post) { return; }
const postStream = this.get('model.postStream'),
lastLoadedPost = postStream.get('lastLoadedPost');
const postStream = this.get('model.postStream');
const lastLoadedPost = postStream.get('posts.lastObject');
this.set('controllers.topic-progress.progressPosition', postStream.progressIndexOfPost(post));

View File

@ -1,6 +1,8 @@
import { exportUserArchive } from 'discourse/lib/export-csv';
import CanCheckEmails from 'discourse/mixins/can-check-emails';
import computed from 'ember-addons/ember-computed-decorators';
import UserAction from 'discourse/models/user-action';
import User from 'discourse/models/user';
export default Ember.Controller.extend(CanCheckEmails, {
indexStream: false,
@ -10,7 +12,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
@computed("content.username")
viewingSelf(username) {
return username === Discourse.User.currentProp('username');
return username === User.currentProp('username');
},
@computed('indexStream', 'viewingSelf', 'forceExpand')
@ -39,13 +41,13 @@ export default Ember.Controller.extend(CanCheckEmails, {
@computed("userActionType")
privateMessageView(userActionType) {
return (userActionType === Discourse.UserAction.TYPES.messages_sent) ||
(userActionType === Discourse.UserAction.TYPES.messages_received);
return (userActionType === UserAction.TYPES.messages_sent) ||
(userActionType === UserAction.TYPES.messages_received);
},
@computed()
canInviteToForum() {
return Discourse.User.currentProp('can_invite_to_forum');
return User.currentProp('can_invite_to_forum');
},
canDeleteUser: Ember.computed.and("model.can_be_deleted", "model.can_delete_all_posts"),
@ -66,13 +68,28 @@ export default Ember.Controller.extend(CanCheckEmails, {
privateMessagesMineActive: Em.computed.equal('pmView', 'mine'),
privateMessagesUnreadActive: Em.computed.equal('pmView', 'unread'),
@computed('model.private_messages_stats.groups', 'groupFilter', 'pmView')
groupPMStats(stats,filter,pmView) {
if (stats) {
return stats.map(g => {
return {
name: g.name,
count: g.count,
active: (g.name === filter && pmView === 'groups')
};
});
}
},
actions: {
expandProfile() {
this.set('forceExpand', true);
},
adminDelete() {
Discourse.AdminUser.find(this.get('model.username').toLowerCase())
// I really want this deferred, don't want to bring in all this code till used
const AdminUser = require('admin/models/admin-user').default;
AdminUser.find(this.get('model.username').toLowerCase())
.then(user => user.destroy({deletePosts: true}));
},

View File

@ -14,8 +14,11 @@ Discourse.Dialect.inlineRegexp({
var username = matches[1],
mentionLookup = this.dialect.options.mentionLookup;
if (mentionLookup && mentionLookup(username.substr(1))) {
var type = mentionLookup && mentionLookup(username.substr(1));
if (type === "user") {
return ['a', {'class': 'mention', href: Discourse.getURL("/users/") + username.substr(1).toLowerCase()}, username];
} else if (type === "group") {
return ['a', {'class': 'mention-group', href: Discourse.getURL("/groups/") + username.substr(1)}, username];
} else {
return ['span', {'class': 'mention'}, username];
}

View File

@ -32,7 +32,10 @@ export default {
// Observe file changes
messageBus.subscribe("/file-change", function(data) {
Ember.TEMPLATES.empty = Handlebars.compile("<div></div>");
if (Handlebars.compile && !Ember.TEMPLATES.empty) {
// hbs notifications only happen in dev
Ember.TEMPLATES.empty = Handlebars.compile("<div></div>");
}
_.each(data,function(me) {
if (me === "refresh") {

View File

@ -12,6 +12,7 @@ export default {
const overrides = PreloadStore.get('translationOverrides') || {};
Object.keys(overrides).forEach(k => {
const v = overrides[k];
k = k.replace('admin_js', 'js');
const segs = k.split('.');
let node = I18n.translations[I18n.locale];

View File

@ -6,7 +6,7 @@
export var CANCELLED_STATUS = "__CANCELLED";
const allowedLettersRegex = /[\s\t\[\{\(]/;
const allowedLettersRegex = /[\s\t\[\{\(\/]/;
var keys = {
backSpace: 8,

View File

@ -6,7 +6,7 @@ export default {
if (Discourse.Utilities.selectedText() !== "") { return false; }
var $link = $(e.currentTarget);
if ($link.hasClass('lightbox')) { return true; }
if ($link.hasClass('lightbox') || $link.hasClass('mention-group')) { return true; }
var href = $link.attr('href') || $link.data('href'),
$article = $link.closest('article'),

View File

@ -11,7 +11,8 @@ let lastAction = -1;
const focusTrackerKey = "focus-tracker";
const idleThresholdTime = 1000 * 10; // 10 seconds
const keyValueStore = new KeyValueStore("discourse_desktop_notifications_");
const context = "discourse_desktop_notifications_";
const keyValueStore = new KeyValueStore(context);
// Called from an initializer
function init(messageBus) {
@ -60,7 +61,7 @@ function setupNotifications() {
window.addEventListener("storage", function(e) {
// note: This event only fires when other tabs setItem()
const key = e.key;
if (key !== focusTrackerKey) {
if (key !== `${context}${focusTrackerKey}`) {
return true;
}
primaryTab = false;

View File

@ -57,57 +57,59 @@
stringCompatHelper("with");
RawHandlebars.Compiler = function() {};
RawHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
if (Handlebars.Compiler) {
RawHandlebars.Compiler = function() {};
RawHandlebars.Compiler.prototype = objectCreate(Handlebars.Compiler.prototype);
RawHandlebars.Compiler.prototype.compiler = RawHandlebars.Compiler;
RawHandlebars.JavaScriptCompiler = function() {};
RawHandlebars.JavaScriptCompiler = function() {};
RawHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
RawHandlebars.JavaScriptCompiler.prototype.compiler = RawHandlebars.JavaScriptCompiler;
RawHandlebars.JavaScriptCompiler.prototype.namespace = "Discourse.EmberCompatHandlebars";
RawHandlebars.JavaScriptCompiler.prototype = objectCreate(Handlebars.JavaScriptCompiler.prototype);
RawHandlebars.JavaScriptCompiler.prototype.compiler = RawHandlebars.JavaScriptCompiler;
RawHandlebars.JavaScriptCompiler.prototype.namespace = "Discourse.EmberCompatHandlebars";
RawHandlebars.Compiler.prototype.mustache = function(mustache) {
if ( !(mustache.params.length || mustache.hash)) {
RawHandlebars.Compiler.prototype.mustache = function(mustache) {
if ( !(mustache.params.length || mustache.hash)) {
var id = new Handlebars.AST.IdNode([{ part: 'get' }]);
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, mustache.escaped);
}
var id = new Handlebars.AST.IdNode([{ part: 'get' }]);
mustache = new Handlebars.AST.MustacheNode([id].concat([mustache.id]), mustache.hash, mustache.escaped);
}
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
};
RawHandlebars.precompile = function(value, asObject) {
var ast = Handlebars.parse(value);
var options = {
knownHelpers: {
get: true
},
data: true,
stringParams: true
return Handlebars.Compiler.prototype.mustache.call(this, mustache);
};
asObject = asObject === undefined ? true : asObject;
RawHandlebars.precompile = function(value, asObject) {
var ast = Handlebars.parse(value);
var environment = new RawHandlebars.Compiler().compile(ast, options);
return new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject);
};
var options = {
knownHelpers: {
get: true
},
data: true,
stringParams: true
};
asObject = asObject === undefined ? true : asObject;
var environment = new RawHandlebars.Compiler().compile(ast, options);
return new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, asObject);
};
RawHandlebars.compile = function(string) {
var ast = Handlebars.parse(string);
// this forces us to rewrite helpers
var options = { data: true, stringParams: true };
var environment = new RawHandlebars.Compiler().compile(ast, options);
var templateSpec = new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
RawHandlebars.compile = function(string) {
var ast = Handlebars.parse(string);
// this forces us to rewrite helpers
var options = { data: true, stringParams: true };
var environment = new RawHandlebars.Compiler().compile(ast, options);
var templateSpec = new RawHandlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
var template = RawHandlebars.template(templateSpec);
template.isMethod = false;
var template = RawHandlebars.template(templateSpec);
template.isMethod = false;
return template;
};
return template;
};
}
RawHandlebars.get = function(ctx, property, options){
if (options.types && options.data.view) {

View File

@ -97,7 +97,7 @@ export default {
},
quoteReply() {
this._replyToPost();
this.sendToSelectedPost("replyToPost");
// lazy but should work for now
setTimeout(function() {
$('.d-editor .quote').click();

View File

@ -1,10 +1,23 @@
function replaceSpan($e, username) {
$e.replaceWith("<a href='" +
function replaceSpan($e, username, opts) {
if (opts && opts.group) {
var extra = "", extraClass = "";
if (opts.mentionable) {
extra = " data-name='" + username + "' data-mentionable-user-count='" + opts.mentionable.user_count + "' ";
extraClass = " notify";
}
$e.replaceWith("<a href='" +
Discourse.getURL("/groups/") + username +
"' class='mention-group" + extraClass + "'" + extra + ">@" + username + "</a>");
} else {
$e.replaceWith("<a href='" +
Discourse.getURL("/users/") + username.toLowerCase() +
"' class='mention'>@" + username + "</a>");
}
}
const found = [];
const foundGroups = [];
const mentionableGroups = [];
const checked = [];
function updateFound($mentions, usernames) {
@ -14,6 +27,9 @@ function updateFound($mentions, usernames) {
const username = usernames[i];
if (found.indexOf(username.toLowerCase()) !== -1) {
replaceSpan($e, username);
} else if (foundGroups.indexOf(username) !== -1) {
const mentionable = _(mentionableGroups).where({name: username}).first();
replaceSpan($e, username, {group: true, mentionable: mentionable});
} else if (checked.indexOf(username) !== -1) {
$e.addClass('mention-tested');
}
@ -38,6 +54,9 @@ export function linkSeenMentions($elem, siteSettings) {
export function fetchUnseenMentions($elem, usernames) {
return Discourse.ajax("/users/is_local_username", { data: { usernames } }).then(function(r) {
found.push.apply(found, r.valid);
foundGroups.push.apply(foundGroups, r.valid_groups);
mentionableGroups.push.apply(mentionableGroups, r.mentionable_groups);
checked.push.apply(checked, usernames);
return r;
});
}

View File

@ -238,6 +238,7 @@ RSVP.EventTarget.mixin(Discourse.Markdown);
Discourse.Markdown.whiteListTag('a', 'class', 'attachment');
Discourse.Markdown.whiteListTag('a', 'class', 'onebox');
Discourse.Markdown.whiteListTag('a', 'class', 'mention');
Discourse.Markdown.whiteListTag('a', 'class', 'mention-group');
Discourse.Markdown.whiteListTag('a', 'target', '_blank');
Discourse.Markdown.whiteListTag('a', 'rel', 'nofollow');

View File

@ -1,9 +1,4 @@
/**
An object that is responsible for logic related to mobile devices.
@namespace Discourse
@module Mobile
**/
// An object that is responsible for logic related to mobile devices.
Discourse.Mobile = {
isMobileDevice: false,
mobileView: false,

View File

@ -21,7 +21,7 @@ const PageTracker = Ember.Object.extend(Ember.Evented, {
router.on('didTransition', function() {
this.send('refreshTitle');
var url = this.get('url');
var url = Discourse.getURL(this.get('url'));
// Refreshing the title is debounced, so we need to trigger this in the
// next runloop to have the correct title.

View File

@ -0,0 +1,59 @@
import { Placeholder } from 'discourse/views/cloaked';
import { default as computed } from 'ember-addons/ember-computed-decorators';
export default Ember.Object.extend(Ember.Array, {
posts: null,
_appendingIds: null,
init() {
this._appendingIds = {};
},
@computed
length() {
return this.get('posts.length') + Object.keys(this._appendingIds || {}).length;
},
_changeArray(cb, offset, removed, inserted) {
this.arrayContentWillChange(offset, removed, inserted);
cb();
this.arrayContentDidChange(offset, removed, inserted);
this.propertyDidChange('length');
},
clear(cb) {
this._changeArray(cb, 0, this.get('posts.length'), 0);
},
appendPost(cb) {
this._changeArray(cb, this.get('posts.length'), 0, 1);
},
removePost(cb) {
this._changeArray(cb, this.get('posts.length') - 1, 1, 0);
},
appending(postIds) {
this._changeArray(() => {
const appendingIds = this._appendingIds;
postIds.forEach(pid => appendingIds[pid] = true);
}, this.get('length'), 0, postIds.length);
},
finishedAppending(postIds) {
this._changeArray(() => {
const appendingIds = this._appendingIds;
postIds.forEach(pid => delete appendingIds[pid]);
}, this.get('posts.length') - postIds.length, postIds.length, postIds.length);
},
finishedPrepending(postIds) {
this._changeArray(Ember.K, 0, 0, postIds.length);
},
objectAt(index) {
const posts = this.get('posts');
return (index < posts.length) ? posts[index] : new Placeholder('post-placeholder');
},
});

View File

@ -15,14 +15,13 @@ const DiscourseURL = Ember.Object.createWithMixins({
Jumps to a particular post in the stream
**/
jumpToPost: function(postNumber, opts) {
const holderId = '#post-cloak-' + postNumber;
const holderId = `.post-cloak[data-post-number=${postNumber}]`;
const offset = function() {
const offset = function(){
const $header = $('header'),
$title = $('#topic-title'),
windowHeight = $(window).height() - $title.height(),
expectedOffset = $title.height() - $header.find('.contents').height() + (windowHeight / 5);
const $header = $('header');
const $title = $('#topic-title');
const windowHeight = $(window).height() - $title.height();
const expectedOffset = $title.height() - $header.find('.contents').height() + (windowHeight / 5);
return $header.outerHeight(true) + ((expectedOffset < 0) ? 0 : expectedOffset);
};
@ -203,40 +202,40 @@ const DiscourseURL = Ember.Object.createWithMixins({
@param {String} oldPath the previous path we were on
@param {String} path the path we're navigating to
**/
navigatedToPost: function(oldPath, path) {
const newMatches = this.TOPIC_REGEXP.exec(path),
newTopicId = newMatches ? newMatches[2] : null;
navigatedToPost(oldPath, path) {
const newMatches = this.TOPIC_REGEXP.exec(path);
const newTopicId = newMatches ? newMatches[2] : null;
if (newTopicId) {
const oldMatches = this.TOPIC_REGEXP.exec(oldPath),
oldTopicId = oldMatches ? oldMatches[2] : null;
const oldMatches = this.TOPIC_REGEXP.exec(oldPath);
const oldTopicId = oldMatches ? oldMatches[2] : null;
// If the topic_id is the same
if (oldTopicId === newTopicId) {
DiscourseURL.replaceState(path);
const container = Discourse.__container__,
topicController = container.lookup('controller:topic'),
opts = {},
postStream = topicController.get('model.postStream');
const container = Discourse.__container__;
const topicController = container.lookup('controller:topic');
const opts = {};
const postStream = topicController.get('model.postStream');
if (newMatches[3]) opts.nearPost = newMatches[3];
if (newMatches[3]) { opts.nearPost = newMatches[3]; }
if (path.match(/last$/)) { opts.nearPost = topicController.get('model.highest_post_number'); }
const closest = opts.nearPost || 1;
const self = this;
postStream.refresh(opts).then(function() {
postStream.refresh(opts).then(() => {
topicController.setProperties({
'model.currentPost': closest,
enteredAt: new Date().getTime().toString()
});
const closestPost = postStream.closestPostForPostNumber(closest),
progress = postStream.progressIndexOfPost(closestPost),
progressController = container.lookup('controller:topic-progress');
const closestPost = postStream.closestPostForPostNumber(closest);
const progress = postStream.progressIndexOfPost(closestPost);
const progressController = container.lookup('controller:topic-progress');
progressController.set('progressPosition', progress);
self.appEvents.trigger('post:highlight', closest);
}).then(function() {
this.appEvents.trigger('post:highlight', closest);
}).then(() => {
DiscourseURL.jumpToPost(closest, {skipIfOnScreen: true});
});

View File

@ -6,7 +6,7 @@ var cache = {},
currentTerm,
oldSearch;
function performSearch(term, topicId, includeGroups, allowedUsers, resultsFn) {
function performSearch(term, topicId, includeGroups, includeMentionableGroups, allowedUsers, resultsFn) {
var cached = cache[term];
if (cached) {
resultsFn(cached);
@ -18,6 +18,7 @@ function performSearch(term, topicId, includeGroups, allowedUsers, resultsFn) {
data: { term: term,
topic_id: topicId,
include_groups: includeGroups,
include_mentionable_groups: includeMentionableGroups,
topic_allowed_users: allowedUsers }
});
@ -76,6 +77,7 @@ function organizeResults(r, options) {
export default function userSearch(options) {
var term = options.term || "",
includeGroups = options.includeGroups,
includeMentionableGroups = options.includeMentionableGroups,
allowedUsers = options.allowedUsers,
topicId = options.topicId;
@ -103,7 +105,7 @@ export default function userSearch(options) {
resolve(CANCELLED_STATUS);
}, 5000);
debouncedSearch(term, topicId, includeGroups, allowedUsers, function(r) {
debouncedSearch(term, topicId, includeGroups, includeMentionableGroups, allowedUsers, function(r) {
clearTimeout(clearPromise);
resolve(organizeResults(r, options));
});

View File

@ -25,7 +25,17 @@ export default Ember.Mixin.create({
const buffer = [];
this.renderString(buffer);
// Chrome likes scrolling after HTML is set
// This happens if you navigate back and forth a few times
// Before removing this code confirm that this does not cause scrolling
// 1. Sort by views
// 2. Go to last post on one of the topics
// 3. Hit back
// 4. Go to last post on same topic
// 5. Expand likes
const scrollTop = $(window).scrollTop();
$sel.html(buffer.join(''));
$(window).scrollTop(scrollTop);
},
rerenderString() {

View File

@ -11,7 +11,7 @@ CategoryList.reopenClass({
const users = Discourse.Model.extractByKey(result.featured_users, Discourse.User);
const list = Discourse.Category.list();
result.category_list.categories.forEach(function(c) {
result.category_list.categories.forEach(c => {
if (c.parent_category_id) {
c.parentCategory = list.findBy('id', c.parent_category_id);
}

View File

@ -1,5 +1,6 @@
import RestModel from 'discourse/models/rest';
import { on } from 'ember-addons/ember-computed-decorators';
import PermissionType from 'discourse/models/permission-type';
const Category = RestModel.extend({
@ -15,16 +16,16 @@ const Category = RestModel.extend({
availableGroups.removeObject(elem.group_name);
return {
group_name: elem.group_name,
permission: Discourse.PermissionType.create({id: elem.permission_type})
permission: PermissionType.create({id: elem.permission_type})
};
}));
}
},
availablePermissions: function(){
return [ Discourse.PermissionType.create({id: Discourse.PermissionType.FULL}),
Discourse.PermissionType.create({id: Discourse.PermissionType.CREATE_POST}),
Discourse.PermissionType.create({id: Discourse.PermissionType.READONLY})
return [ PermissionType.create({id: PermissionType.FULL}),
PermissionType.create({id: PermissionType.CREATE_POST}),
PermissionType.create({id: PermissionType.READONLY})
];
}.property(),
@ -86,6 +87,7 @@ const Category = RestModel.extend({
custom_fields: this.get('custom_fields'),
topic_template: this.get('topic_template'),
suppress_from_homepage: this.get('suppress_from_homepage'),
contains_messages: this.get("contains_messages"),
},
type: this.get('id') ? 'PUT' : 'POST'
});
@ -116,9 +118,9 @@ const Category = RestModel.extend({
permissions: function(){
return Em.A([
{group_name: "everyone", permission: Discourse.PermissionType.create({id: 1})},
{group_name: "admins", permission: Discourse.PermissionType.create({id: 2}) },
{group_name: "crap", permission: Discourse.PermissionType.create({id: 3}) }
{group_name: "everyone", permission: PermissionType.create({id: 1})},
{group_name: "admins", permission: PermissionType.create({id: 2}) },
{group_name: "crap", permission: PermissionType.create({id: 3}) }
]);
}.property(),

View File

@ -67,11 +67,13 @@ const Composer = RestModel.extend({
creatingPrivateMessage: Em.computed.equal('action', PRIVATE_MESSAGE),
notCreatingPrivateMessage: Em.computed.not('creatingPrivateMessage'),
showCategoryChooser: function(){
@computed("privateMessage", "archetype.hasOptions", "categoryId")
showCategoryChooser(isPrivateMessage, hasOptions, categoryId) {
const manyCategories = Discourse.Category.list().length > 1;
const hasOptions = this.get('archetype.hasOptions');
return !this.get('privateMessage') && (hasOptions || manyCategories);
}.property('privateMessage'),
const category = Discourse.Category.findById(categoryId);
const containsMessages = category && category.get("contains_messages");
return !isPrivateMessage && !containsMessages && (hasOptions || manyCategories);
},
privateMessage: function(){
return this.get('creatingPrivateMessage') || this.get('topic.archetype') === 'private_message';

View File

@ -98,7 +98,8 @@ const Group = Discourse.Model.extend({
automatic_membership_retroactive: !!this.get('automatic_membership_retroactive'),
title: this.get('title'),
primary_group: !!this.get('primary_group'),
grant_trust_level: this.get('grant_trust_level')
grant_trust_level: this.get('grant_trust_level'),
incoming_email: this.get("incoming_email"),
};
},
@ -121,16 +122,26 @@ const Group = Discourse.Model.extend({
findPosts(opts) {
opts = opts || {};
const type = opts['type'] || 'posts';
var data = {};
if (opts.beforePostId) { data.before_post_id = opts.beforePostId; }
return Discourse.ajax("/groups/" + this.get('name') + "/posts.json", { data: data }).then(function (posts) {
return posts.map(function (p) {
return Discourse.ajax(`/groups/${this.get('name')}/${type}.json`, { data: data }).then(posts => {
return posts.map(p => {
p.user = Discourse.User.create(p.user);
return Em.Object.create(p);
});
});
}
},
setNotification(notification_level) {
this.set("notification_level", notification_level);
return Discourse.ajax(`/groups/${this.get("name")}/notifications`, {
data: { notification_level },
type: "POST"
});
},
});
Group.reopenClass({

View File

@ -1,5 +1,4 @@
Discourse.PermissionType = Discourse.Model.extend({
const PermissionType = Discourse.Model.extend({
description: function(){
var key = "";
@ -18,6 +17,8 @@ Discourse.PermissionType = Discourse.Model.extend({
}.property("id")
});
Discourse.PermissionType.FULL = 1;
Discourse.PermissionType.CREATE_POST = 2;
Discourse.PermissionType.READONLY = 3;
PermissionType.FULL = 1;
PermissionType.CREATE_POST = 2;
PermissionType.READONLY = 3;
export default PermissionType;

View File

@ -1,5 +1,8 @@
import DiscourseURL from 'discourse/lib/url';
import RestModel from 'discourse/models/rest';
import PostsWithPlaceholders from 'discourse/lib/posts-with-placeholders';
import { default as computed } from 'ember-addons/ember-computed-decorators';
import { loadTopicView } from 'discourse/models/topic';
function calcDayDiff(p1, p2) {
if (!p1) { return; }
@ -16,84 +19,105 @@ function calcDayDiff(p1, p2) {
}
}
const PostStream = RestModel.extend({
loading: Em.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'),
notLoading: Em.computed.not('loading'),
filteredPostsCount: Em.computed.alias("stream.length"),
export default RestModel.extend({
_identityMap: null,
posts: null,
stream: null,
userFilters: null,
summary: null,
loaded: null,
loadingAbove: null,
loadingBelow: null,
loadingFilter: null,
stagingPost: null,
postsWithPlaceholders: null,
hasPosts: function() {
init() {
this._identityMap = {};
const posts = [];
const postsWithPlaceholders = PostsWithPlaceholders.create({ posts, store: this.store });
this.setProperties({
posts,
postsWithPlaceholders,
stream: [],
userFilters: [],
summary: false,
loaded: false,
loadingAbove: false,
loadingBelow: false,
loadingFilter: false,
stagingPost: false,
});
},
loading: Ember.computed.or('loadingAbove', 'loadingBelow', 'loadingFilter', 'stagingPost'),
notLoading: Ember.computed.not('loading'),
filteredPostsCount: Ember.computed.alias("stream.length"),
@computed('posts.@each')
hasPosts() {
return this.get('posts.length') > 0;
}.property("posts.@each"),
},
hasStream: Em.computed.gt('filteredPostsCount', 0),
canAppendMore: Em.computed.and('notLoading', 'hasPosts', 'lastPostNotLoaded'),
canPrependMore: Em.computed.and('notLoading', 'hasPosts', 'firstPostNotLoaded'),
@computed('hasPosts', 'filteredPostsCount')
hasLoadedData(hasPosts, filteredPostsCount) {
return hasPosts && filteredPostsCount > 0;
},
firstPostPresent: function() {
if (!this.get('hasLoadedData')) { return false; }
return !!this.get('posts').findProperty('id', this.get('firstPostId'));
}.property('hasLoadedData', 'posts.@each', 'firstPostId'),
canAppendMore: Ember.computed.and('notLoading', 'hasPosts', 'lastPostNotLoaded'),
canPrependMore: Ember.computed.and('notLoading', 'hasPosts', 'firstPostNotLoaded'),
firstPostNotLoaded: Em.computed.not('firstPostPresent'),
@computed('hasLoadedData', 'firstPostId', 'posts.@each')
firstPostPresent(hasLoadedData, firstPostId) {
if (!hasLoadedData) { return false; }
return !!this.get('posts').findProperty('id', firstPostId);
},
firstLoadedPost: function() {
return _.first(this.get('posts'));
}.property('posts.@each'),
firstPostNotLoaded: Ember.computed.not('firstPostPresent'),
firstPostId: Ember.computed.alias('stream.firstObject'),
lastPostId: Ember.computed.alias('stream.lastObject'),
lastLoadedPost: function() {
return _.last(this.get('posts'));
}.property('posts.@each'),
@computed('hasLoadedData', 'lastPostId', 'posts.@each.id')
loadedAllPosts(hasLoadedData, lastPostId) {
if (!hasLoadedData) { return false; }
if (lastPostId === -1) { return true; }
firstPostId: function() {
return this.get('stream')[0];
}.property('stream.@each'),
return !!this.get('posts').findProperty('id', lastPostId);
},
lastPostId: function() {
return _.last(this.get('stream'));
}.property('stream.@each'),
loadedAllPosts: function() {
if (!this.get('hasLoadedData')) {
return false;
}
// if we are staging a post assume all is loaded
if (this.get('lastPostId') === -1) {
return true;
}
return !!this.get('posts').findProperty('id', this.get('lastPostId'));
}.property('hasLoadedData', 'posts.@each.id', 'lastPostId'),
lastPostNotLoaded: Em.computed.not('loadedAllPosts'),
lastPostNotLoaded: Ember.computed.not('loadedAllPosts'),
/**
Returns a JS Object of current stream filter options. It should match the query
params for the stream.
**/
streamFilters: function() {
@computed('summary', 'show_deleted', 'userFilters.[]')
streamFilters(summary, showDeleted) {
const result = {};
if (this.get('summary')) { result.filter = "summary"; }
if (this.get('show_deleted')) { result.show_deleted = true; }
if (summary) { result.filter = "summary"; }
if (showDeleted) { result.show_deleted = true; }
const userFilters = this.get('userFilters');
if (!Em.isEmpty(userFilters)) {
if (!Ember.isEmpty(userFilters)) {
result.username_filters = userFilters.join(",");
}
return result;
}.property('userFilters.[]', 'summary', 'show_deleted'),
},
hasNoFilters: function() {
@computed('streamFilters.[]', 'topic.posts_count', 'posts.length')
hasNoFilters() {
const streamFilters = this.get('streamFilters');
return !(streamFilters && ((streamFilters.filter === 'summary') || streamFilters.username_filters));
}.property('streamFilters.[]', 'topic.posts_count', 'posts.length'),
},
/**
Returns the window of posts above the current set in the stream, bound to the top of the stream.
This is the collection we'll ask for when scrolling upwards.
**/
previousWindow: function() {
@computed('posts.@each', 'stream.@each')
previousWindow() {
// If we can't find the last post loaded, bail
const firstPost = _.first(this.get('posts'));
if (!firstPost) { return []; }
@ -106,16 +130,15 @@ const PostStream = RestModel.extend({
let startIndex = firstIndex - this.get('topic.chunk_size');
if (startIndex < 0) { startIndex = 0; }
return stream.slice(startIndex, firstIndex);
}.property('posts.@each', 'stream.@each'),
},
/**
Returns the window of posts below the current set in the stream, bound by the bottom of the
stream. This is the collection we use when scrolling downwards.
**/
nextWindow: function() {
@computed('posts.lastObject', 'stream.@each')
nextWindow(lastLoadedPost) {
// If we can't find the last post loaded, bail
const lastLoadedPost = this.get('lastLoadedPost');
if (!lastLoadedPost) { return []; }
// Find the index of the last post loaded, if not found, bail
@ -126,7 +149,7 @@ const PostStream = RestModel.extend({
// find our window of posts
return stream.slice(lastIndex+1, lastIndex + this.get('topic.chunk_size') + 1);
}.property('lastLoadedPost', 'stream.@each'),
},
cancelFilter() {
this.set('summary', false);
@ -138,10 +161,9 @@ const PostStream = RestModel.extend({
this.get('userFilters').clear();
this.toggleProperty('summary');
const self = this;
return this.refresh().then(function() {
if (self.get('summary')) {
self.jumpToSecondVisible();
return this.refresh().then(() => {
if (this.get('summary')) {
this.jumpToSecondVisible();
}
});
},
@ -172,10 +194,9 @@ const PostStream = RestModel.extend({
userFilters.addObject(username);
jump = true;
}
const self = this;
return this.refresh().then(function() {
return this.refresh().then(() => {
if (jump) {
self.jumpToSecondVisible();
this.jumpToSecondVisible();
}
});
},
@ -189,7 +210,6 @@ const PostStream = RestModel.extend({
opts.nearPost = parseInt(opts.nearPost, 10);
const topic = this.get('topic');
const self = this;
// Do we already have the post in our list of posts? Jump there.
if (opts.forceLoad) {
@ -200,25 +220,23 @@ const PostStream = RestModel.extend({
}
// TODO: if we have all the posts in the filter, don't go to the server for them.
self.set('loadingFilter', true);
this.set('loadingFilter', true);
opts = _.merge(opts, self.get('streamFilters'));
opts = _.merge(opts, this.get('streamFilters'));
// Request a topicView
return PostStream.loadTopicView(topic.get('id'), opts).then(function (json) {
topic.updateFromJson(json);
self.updateFromJson(json.post_stream);
self.setProperties({ loadingFilter: false, loaded: true });
}).catch(function(result) {
self.errorLoading(result);
return loadTopicView(topic, opts).then(json => {
this.updateFromJson(json.post_stream);
this.setProperties({ loadingFilter: false, loaded: true });
}).catch(result => {
this.errorLoading(result);
throw result;
});
},
hasLoadedData: Em.computed.and('hasPosts', 'hasStream'),
collapsePosts(from, to){
const posts = this.get('posts');
const remove = posts.filter(function(post){
const remove = posts.filter(post => {
const postNumber = post.get('post_number');
return postNumber >= from && postNumber <= to;
});
@ -228,44 +246,39 @@ const PostStream = RestModel.extend({
// make gap
this.set('gaps', this.get('gaps') || {before: {}, after: {}});
const before = this.get('gaps.before');
const post = posts.find(p => p.get('post_number') > to);
const post = posts.find(function(p){
return p.get('post_number') > to;
});
before[post.get('id')] = remove.map(function(p){
return p.get('id');
});
before[post.get('id')] = remove.map(p => p.get('id'));
post.set('hasGap', true);
this.get('stream').enumerableContentDidChange();
},
// Fill in a gap of posts before a particular post
fillGapBefore(post, gap) {
const postId = post.get('id'),
stream = this.get('stream'),
idx = stream.indexOf(postId),
currentPosts = this.get('posts'),
self = this;
stream = this.get('stream'),
idx = stream.indexOf(postId),
currentPosts = this.get('posts');
if (idx !== -1) {
// Insert the gap at the appropriate place
stream.splice.apply(stream, [idx, 0].concat(gap));
let postIdx = currentPosts.indexOf(post);
const origIdx = postIdx;
if (postIdx !== -1) {
return this.findPostsByIds(gap).then(function(posts) {
posts.forEach(function(p) {
const stored = self.storePost(p);
return this.findPostsByIds(gap).then(posts => {
posts.forEach(p => {
const stored = this.storePost(p);
if (!currentPosts.contains(stored)) {
currentPosts.insertAt(postIdx++, stored);
}
});
delete self.get('gaps.before')[postId];
self.get('stream').enumerableContentDidChange();
delete this.get('gaps.before')[postId];
this.get('stream').enumerableContentDidChange();
this.get('postsWithPlaceholders').arrayContentDidChange(origIdx, 0, posts.length);
post.set('hasGap', false);
});
}
@ -297,31 +310,32 @@ const PostStream = RestModel.extend({
if (Ember.isEmpty(postIds)) { return Ember.RSVP.resolve(); }
this.set('loadingBelow', true);
const stopLoading = () => this.set('loadingBelow', false);
return this.findPostsByIds(postIds).then((posts) => {
const postsWithPlaceholders = this.get('postsWithPlaceholders');
postsWithPlaceholders.appending(postIds);
return this.findPostsByIds(postIds).then(posts => {
posts.forEach(p => this.appendPost(p));
stopLoading();
}, stopLoading);
return posts;
}).finally(() => {
postsWithPlaceholders.finishedAppending(postIds);
this.set('loadingBelow', false);
});
},
// Prepend the previous window of posts to the stream. Call it when scrolling upwards.
prependMore() {
const postStream = this;
// Make sure we can append more posts
if (!postStream.get('canPrependMore')) { return Ember.RSVP.resolve(); }
if (!this.get('canPrependMore')) { return Ember.RSVP.resolve(); }
const postIds = postStream.get('previousWindow');
const postIds = this.get('previousWindow');
if (Ember.isEmpty(postIds)) { return Ember.RSVP.resolve(); }
postStream.set('loadingAbove', true);
return postStream.findPostsByIds(postIds.reverse()).then(function(posts) {
posts.forEach(function(p) {
postStream.prependPost(p);
});
postStream.set('loadingAbove', false);
this.set('loadingAbove', true);
return this.findPostsByIds(postIds.reverse()).then(posts => {
posts.forEach(p => this.prependPost(p));
}).finally(() => {
const postsWithPlaceholders = this.get('postsWithPlaceholders');
postsWithPlaceholders.finishedPrepending(postIds);
this.set('loadingAbove', false);
});
},
@ -372,8 +386,7 @@ const PostStream = RestModel.extend({
}
this.get('stream').removeObject(-1);
this.get('postIdentityMap').set(-1, null);
this._identityMap[-1] = null;
this.set('stagingPost', false);
},
@ -383,8 +396,8 @@ const PostStream = RestModel.extend({
**/
undoPost(post) {
this.get('stream').removeObject(-1);
this.posts.removeObject(post);
this.get('postIdentityMap').set(-1, null);
this.get('postsWithPlaceholders').removePost(() => this.posts.removeObject(post));
this._identityMap[-1] = null;
const topic = this.get('topic');
this.set('stagingPost', false);
@ -414,7 +427,13 @@ const PostStream = RestModel.extend({
const posts = this.get('posts');
calcDayDiff(stored, this.get('lastAppended'));
posts.addObject(stored);
if (!posts.contains(stored)) {
if (!this.get('loadingBelow')) {
this.get('postsWithPlaceholders').appendPost(() => posts.pushObject(stored));
} else {
posts.pushObject(stored);
}
}
if (stored.get('id') !== -1) {
this.set('lastAppended', stored);
@ -424,21 +443,19 @@ const PostStream = RestModel.extend({
},
removePosts(posts) {
if (Em.isEmpty(posts)) { return; }
if (Ember.isEmpty(posts)) { return; }
const postIds = posts.map(function (p) { return p.get('id'); });
const identityMap = this.get('postIdentityMap');
const postIds = posts.map(p => p.get('id'));
const identityMap = this._identityMap;
this.get('stream').removeObjects(postIds);
this.get('posts').removeObjects(posts);
postIds.forEach(function(id){
identityMap.delete(id);
});
postIds.forEach(id => delete identityMap[id]);
},
// Returns a post from the identity map if it's been inserted.
findLoadedPost(id) {
return this.get('postIdentityMap').get(id);
return this._identityMap[id];
},
loadPost(postId){
@ -465,34 +482,34 @@ const PostStream = RestModel.extend({
this.get('stream').addObject(postId);
if (loadedAllPosts) {
this.set('loadingLastPost', true);
this.appendMore().finally(
()=>this.set('loadingLastPost', true)
);
this.findPostsByIds([postId]).then(posts => {
posts.forEach(p => this.appendPost(p));
}).finally(() => {
this.set('loadingLastPost', false);
});
}
}
},
triggerRecoveredPost(postId){
const self = this,
postIdentityMap = this.get('postIdentityMap'),
existing = postIdentityMap.get(postId);
triggerRecoveredPost(postId) {
const existing = this._identityMap[postId];
if(existing){
if (existing) {
this.triggerChangedPost(postId, new Date());
} else {
// need to insert into stream
const url = "/posts/" + postId;
const store = this.store;
Discourse.ajax(url).then(function(p){
Discourse.ajax(url).then(p => {
const post = store.createRecord('post', p);
const stream = self.get("stream");
const posts = self.get("posts");
self.storePost(post);
const stream = this.get("stream");
const posts = this.get("posts");
this.storePost(post);
// we need to zip this into the stream
let index = 0;
stream.forEach(function(pid){
if (pid < p.id){
stream.forEach(pid => {
if (pid < p.id) {
index+= 1;
}
});
@ -500,17 +517,17 @@ const PostStream = RestModel.extend({
stream.insertAt(index, p.id);
index = 0;
posts.forEach(function(_post){
if(_post.id < p.id){
posts.forEach(_post => {
if (_post.id < p.id) {
index+= 1;
}
});
if(index < posts.length){
if (index < posts.length) {
posts.insertAt(index, post);
} else {
if(post.post_number < posts[posts.length-1].post_number + 5){
self.appendMore();
if (post.post_number < posts[posts.length-1].post_number + 5) {
this.appendMore();
}
}
});
@ -518,50 +535,38 @@ const PostStream = RestModel.extend({
},
triggerDeletedPost(postId){
const self = this,
postIdentityMap = this.get('postIdentityMap'),
existing = postIdentityMap.get(postId);
const existing = this._identityMap[postId];
if(existing){
if (existing) {
const url = "/posts/" + postId;
const store = this.store;
Discourse.ajax(url).then(
function(p){
self.storePost(store.createRecord('post', p));
},
function(){
self.removePosts([existing]);
});
Discourse.ajax(url).then(p => {
this.storePost(store.createRecord('post', p));
}).catch(() => {
this.removePosts([existing]);
});
}
},
triggerChangedPost(postId, updatedAt) {
if (!postId) { return; }
const postIdentityMap = this.get('postIdentityMap'),
existing = postIdentityMap.get(postId),
self = this;
const existing = this._identityMap[postId];
if (existing && existing.updated_at !== updatedAt) {
const url = "/posts/" + postId;
const store = this.store;
Discourse.ajax(url).then(function(p){
self.storePost(store.createRecord('post', p));
});
Discourse.ajax(url).then(p => this.storePost(store.createRecord('post', p)));
}
},
// Returns the "thread" of posts in the history of a post.
findReplyHistory(post) {
const postStream = this,
url = "/posts/" + post.get('id') + "/reply-history.json?max_replies=" + Discourse.SiteSettings.max_reply_history;
const url = `/posts/${post.get('id')}/reply-history.json?max_replies=${Discourse.SiteSettings.max_reply_history}`;
const store = this.store;
return Discourse.ajax(url).then(function(result) {
return result.map(function (p) {
return postStream.storePost(store.createRecord('post', p));
});
}).then(function (replyHistory) {
return Discourse.ajax(url).then(result => {
return result.map(p => this.storePost(store.createRecord('post', p)));
}).then(replyHistory => {
post.set('replyHistory', replyHistory);
});
},
@ -575,7 +580,7 @@ const PostStream = RestModel.extend({
if (!this.get('hasPosts')) { return; }
let closest = null;
this.get('posts').forEach(function (p) {
this.get('posts').forEach(p => {
if (!closest) {
closest = p;
return;
@ -589,20 +594,14 @@ const PostStream = RestModel.extend({
return closest;
},
/**
Get the index of a post in the stream. (Use this for the topic progress bar.)
@param post the post to get the index of
@returns {Number} 1-starting index of the post, or 0 if not found
@see PostStream.progressIndexOfPostId
**/
// Get the index of a post in the stream. (Use this for the topic progress bar.)
progressIndexOfPost(post) {
return this.progressIndexOfPostId(post.get('id'));
},
// Get the index in the stream of a post id. (Use this for the topic progress bar.)
progressIndexOfPostId(post_id) {
return this.get('stream').indexOf(post_id) + 1;
progressIndexOfPostId(postId) {
return this.get('stream').indexOf(postId) + 1;
},
/**
@ -614,7 +613,7 @@ const PostStream = RestModel.extend({
if (!this.get('hasPosts')) { return; }
let closest = null;
this.get('posts').forEach(function (p) {
this.get('posts').forEach(p => {
if (closest === postNumber) { return; }
if (!closest) { closest = p.get('post_number'); }
@ -653,21 +652,20 @@ const PostStream = RestModel.extend({
},
updateFromJson(postStreamData) {
const postStream = this,
posts = this.get('posts');
const posts = this.get('posts');
const postsWithPlaceholders = this.get('postsWithPlaceholders');
postsWithPlaceholders.clear(() => posts.clear());
posts.clear();
this.set('gaps', null);
if (postStreamData) {
// Load posts if present
const store = this.store;
postStreamData.posts.forEach(function(p) {
postStream.appendPost(store.createRecord('post', p));
});
postStreamData.posts.forEach(p => this.appendPost(store.createRecord('post', p)));
delete postStreamData.posts;
// Update our attributes
postStream.setProperties(postStreamData);
this.setProperties(postStreamData);
}
},
@ -677,13 +675,12 @@ const PostStream = RestModel.extend({
than you supplied if the post has already been loaded.
**/
storePost(post) {
// Calling `Em.get(undefined` raises an error
// Calling `Ember.get(undefined)` raises an error
if (!post) { return; }
const postId = Em.get(post, 'id');
const postId = Ember.get(post, 'id');
if (postId) {
const postIdentityMap = this.get('postIdentityMap'),
existing = postIdentityMap.get(post.get('id'));
const existing = this._identityMap[post.get('id')];
// Update the `highest_post_number` if this post is higher.
const postNumber = post.get('post_number');
@ -698,67 +695,41 @@ const PostStream = RestModel.extend({
}
post.set('topic', this.get('topic'));
postIdentityMap.set(post.get('id'), post);
this._identityMap[post.get('id')] = post;
}
return post;
},
/**
Given a list of postIds, returns a list of the posts we don't have in our
identity map and need to load.
**/
listUnloadedIds(postIds) {
const unloaded = Em.A(),
postIdentityMap = this.get('postIdentityMap');
postIds.forEach(function(p) {
if (!postIdentityMap.has(p)) { unloaded.pushObject(p); }
});
return unloaded;
},
findPostsByIds(postIds) {
const unloaded = this.listUnloadedIds(postIds),
postIdentityMap = this.get('postIdentityMap');
const identityMap = this._identityMap;
const unloaded = postIds.filter(p => !identityMap[p]);
// Load our unloaded posts by id
return this.loadIntoIdentityMap(unloaded).then(function() {
return postIds.map(function (p) {
return postIdentityMap.get(p);
}).compact();
return this.loadIntoIdentityMap(unloaded).then(() => {
return postIds.map(p => identityMap[p]).compact();
});
},
loadIntoIdentityMap(postIds) {
// If we don't want any posts, return a promise that resolves right away
if (Em.isEmpty(postIds)) {
return Ember.RSVP.resolve();
}
const url = "/t/" + this.get('topic.id') + "/posts.json",
data = { post_ids: postIds },
postStream = this;
if (Ember.isEmpty(postIds)) { return Ember.RSVP.resolve([]); }
const url = "/t/" + this.get('topic.id') + "/posts.json";
const data = { post_ids: postIds };
const store = this.store;
return Discourse.ajax(url, {data: data}).then(function(result) {
const posts = Em.get(result, "post_stream.posts");
return Discourse.ajax(url, {data}).then(result => {
const posts = Ember.get(result, "post_stream.posts");
if (posts) {
posts.forEach(function (p) {
postStream.storePost(store.createRecord('post', p));
});
posts.forEach(p => this.storePost(store.createRecord('post', p)));
}
});
},
indexOf(post) {
return this.get('stream').indexOf(post.get('id'));
},
/**
Handles an error loading a topic based on a HTTP status code. Updates
the text to the correct values.
**/
// Handles an error loading a topic based on a HTTP status code. Updates
// the text to the correct values.
errorLoading(result) {
const status = result.jqXHR.status;
@ -786,45 +757,4 @@ const PostStream = RestModel.extend({
// Otherwise supply a generic error message
topic.set('message', I18n.t('topic.server_error.description'));
}
});
PostStream.reopenClass({
create() {
const postStream = this._super.apply(this, arguments);
postStream.setProperties({
posts: [],
stream: [],
userFilters: [],
postIdentityMap: Em.Map.create(),
summary: false,
loaded: false,
loadingAbove: false,
loadingBelow: false,
loadingFilter: false,
stagingPost: false
});
return postStream;
},
loadTopicView(topicId, args) {
const opts = _.merge({}, args);
let url = Discourse.getURL("/t/") + topicId;
if (opts.nearPost) {
url += "/" + opts.nearPost;
}
delete opts.nearPost;
delete opts.__type;
delete opts.store;
return PreloadStore.getAndRemove("topic_" + topicId, function() {
return Discourse.ajax(url + ".json", {data: opts});
});
}
});
export default PostStream;

View File

@ -4,6 +4,13 @@ export default Ember.ArrayProxy.extend({
totalRows: 0,
refreshing: false,
content: null,
loadMoreUrl: null,
refreshUrl: null,
findArgs: null,
store: null,
__type: null,
canLoadMore: function() {
return this.get('length') < this.get('totalRows');
}.property('totalRows', 'length'),

View File

@ -63,7 +63,7 @@ export default Ember.Object.extend({
_hydrateFindResults(result, type, findArgs) {
if (typeof findArgs === "object") {
return this._resultSet(type, result);
return this._resultSet(type, result, findArgs);
} else {
return this._hydrate(type, result[Ember.String.underscore(type)], result);
}
@ -81,16 +81,16 @@ export default Ember.Object.extend({
},
find(type, findArgs, opts) {
return this.adapterFor(type).find(this, type, findArgs, opts).then((result) => {
return this.adapterFor(type).find(this, type, findArgs, opts).then(result => {
return this._hydrateFindResults(result, type, findArgs, opts);
});
},
refreshResults(resultSet, type, url) {
const self = this;
return Discourse.ajax(url).then(function(result) {
const typeName = Ember.String.underscore(self.pluralize(type)),
content = result[typeName].map(obj => self._hydrate(type, obj, result));
return Discourse.ajax(url).then(result => {
const typeName = Ember.String.underscore(self.pluralize(type));
const content = result[typeName].map(obj => self._hydrate(type, obj, result));
resultSet.set('content', content);
});
},
@ -142,14 +142,25 @@ export default Ember.Object.extend({
});
},
_resultSet(type, result) {
const typeName = Ember.String.underscore(this.pluralize(type)),
content = result[typeName].map(obj => this._hydrate(type, obj, result)),
totalRows = result["total_rows_" + typeName] || content.length,
loadMoreUrl = result["load_more_" + typeName],
refreshUrl = result['refresh_' + typeName];
_resultSet(type, result, findArgs) {
const typeName = Ember.String.underscore(this.pluralize(type));
const content = result[typeName].map(obj => this._hydrate(type, obj, result));
return ResultSet.create({ content, totalRows, loadMoreUrl, refreshUrl, store: this, __type: type });
const createArgs = {
content,
findArgs,
totalRows: result["total_rows_" + typeName] || content.length,
loadMoreUrl: result["load_more_" + typeName],
refreshUrl: result['refresh_' + typeName],
store: this,
__type: type
};
if (result.extras) {
createArgs.extras = result.extras;
}
return ResultSet.create(createArgs);
},
_build(type, obj) {

View File

@ -3,6 +3,25 @@ import RestModel from 'discourse/models/rest';
import { propertyEqual } from 'discourse/lib/computed';
import { longDate } from 'discourse/lib/formatter';
import computed from 'ember-addons/ember-computed-decorators';
import ActionSummary from 'discourse/models/action-summary';
export function loadTopicView(topic, args) {
const topicId = topic.get('id');
const data = _.merge({}, args);
const url = Discourse.getURL("/t/") + topicId;
const jsonUrl = (data.nearPost ? `${url}/${data.nearPost}` : url) + '.json';
delete data.nearPost;
delete data.__type;
delete data.store;
return PreloadStore.getAndRemove(`topic_${topicId}`, () => {
return Discourse.ajax(jsonUrl, {data});
}).then(json => {
topic.updateFromJson(json);
return json;
});
}
const Topic = RestModel.extend({
message: null,
@ -15,12 +34,12 @@ const Topic = RestModel.extend({
@computed('posters.@each')
lastPoster(posters) {
var user;
if (posters && posters.length > 0) {
const latest = posters.filter(p => p.extras && p.extras.indexOf("latest") >= 0)[0];
return latest.user;
} else {
return this.get("creator");
user = latest && latest.user;
}
return user || this.get("creator");
},
@computed('fancy_title')
@ -318,11 +337,14 @@ const Topic = RestModel.extend({
keys.removeObject('details');
keys.removeObject('post_stream');
const topic = this;
keys.forEach(function (key) {
topic.set(key, json[key]);
});
keys.forEach(key => this.set(key, json[key]));
},
reload() {
const self = this;
return Discourse.ajax('/t/' + this.get('id'), { type: 'GET' }).then(function(topic_json) {
self.updateFromJson(topic_json);
});
},
isPinnedUncategorized: function() {
@ -415,7 +437,7 @@ Topic.reopenClass({
result.actions_summary = result.actions_summary.map(function(a) {
a.post = result;
a.actionType = Discourse.Site.current().postActionTypeById(a.id);
const actionSummary = Discourse.ActionSummary.create(a);
const actionSummary = ActionSummary.create(a);
lookup.set(a.actionType.get('name_key'), actionSummary);
return actionSummary;
});

View File

@ -1,12 +1,7 @@
/**
A data model representing a group of UserActions
@class UserActionGroup
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
Discourse.UserActionGroup = Discourse.Model.extend({
export default Discourse.Model.extend({
push: function(item) {
if (!this.items) {
this.items = [];

View File

@ -2,6 +2,7 @@ import RestModel from 'discourse/models/rest';
import { url } from 'discourse/lib/computed';
import { on } from 'ember-addons/ember-computed-decorators';
import computed from 'ember-addons/ember-computed-decorators';
import UserActionGroup from 'discourse/models/user-action-group';
const UserActionTypes = {
likes_given: 1,
@ -35,7 +36,7 @@ const UserAction = RestModel.extend({
@computed("action_type")
descriptionKey(action) {
if (action === null || Discourse.UserAction.TO_SHOW.indexOf(action) >= 0) {
if (action === null || UserAction.TO_SHOW.indexOf(action) >= 0) {
if (this.get('isPM')) {
return this.get('sameUser') ? 'sent_by_you' : 'sent_by_user';
} else {
@ -111,10 +112,10 @@ const UserAction = RestModel.extend({
let groups = this.get("childGroups");
if (!groups) {
groups = {
likes: Discourse.UserActionGroup.create({ icon: "fa fa-heart" }),
stars: Discourse.UserActionGroup.create({ icon: "fa fa-star" }),
edits: Discourse.UserActionGroup.create({ icon: "fa fa-pencil" }),
bookmarks: Discourse.UserActionGroup.create({ icon: "fa fa-bookmark" })
likes: UserActionGroup.create({ icon: "fa fa-heart" }),
stars: UserActionGroup.create({ icon: "fa fa-star" }),
edits: UserActionGroup.create({ icon: "fa fa-pencil" }),
bookmarks: UserActionGroup.create({ icon: "fa fa-bookmark" })
};
}
this.set("childGroups", groups);
@ -171,7 +172,7 @@ UserAction.reopenClass({
if (found === void 0) {
let current;
if (Discourse.UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
if (UserAction.TO_COLLAPSE.indexOf(item.action_type) >= 0) {
current = UserAction.create(item);
item.switchToActing();
current.addChild(item);

Some files were not shown because too many files have changed in this diff Show More