Version bump
This commit is contained in:
commit
0a4ae61b2c
@ -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
1
.gitignore
vendored
@ -47,6 +47,7 @@ log/
|
||||
!/plugins/emoji/
|
||||
!/plugins/lazyYT/
|
||||
!/plugins/poll/
|
||||
!/plugins/discourse-details/
|
||||
/plugins/*/auto_generated/
|
||||
|
||||
/spec/fixtures/plugins/my_plugin/auto_generated
|
||||
|
||||
2
Gemfile
2
Gemfile
@ -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
|
||||
|
||||
12
Gemfile.lock
12
Gemfile.lock
@ -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
|
||||
|
||||
@ -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.
Binary file not shown.
@ -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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M809 532l266 499h-112l-157 -312q-24 -48 -44 -92l-42 92l-155 312h-120l263 -493v-324h101v318zM1536 1408v-1536h-1536v1536h1536z" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" d="M0 1408h1536v-1536h-1536v1536zM1085 293l-221 631l221 297h-634l221 -297l-221 -631l317 -304z" />
|
||||
<glyph unicode="" 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="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" 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="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
<glyph unicode="" horiz-adv-x="1792" />
|
||||
</font>
|
||||
</defs></svg>
|
||||
|
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 357 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,2 +0,0 @@
|
||||
import CustomizationBase from 'admin/adapters/customization-base';
|
||||
export default CustomizationBase;
|
||||
@ -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.$());
|
||||
}
|
||||
});
|
||||
@ -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;
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -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'));
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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');
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -1 +0,0 @@
|
||||
export default Ember.ArrayController.extend();
|
||||
@ -0,0 +1,3 @@
|
||||
Em.Handlebars.helper('preserve-newlines', str => {
|
||||
return new Handlebars.SafeString(Discourse.Utilities.escapeExpression(str).replace(/\n/g, "<br>"));
|
||||
});
|
||||
200
app/assets/javascripts/admin/lib/autosize.js.es6
Normal file
200
app/assets/javascripts/admin/lib/autosize.js.es6
Normal 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;
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
import RestModel from 'discourse/models/rest';
|
||||
export default RestModel.extend();
|
||||
@ -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'));
|
||||
}
|
||||
});
|
||||
|
||||
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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' });
|
||||
|
||||
@ -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 });
|
||||
}
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
@ -1,5 +0,0 @@
|
||||
export default Discourse.Route.extend({
|
||||
model() {
|
||||
return this.store.findAll('site-text-type');
|
||||
}
|
||||
});
|
||||
@ -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>
|
||||
|
||||
@ -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"}}
|
||||
@ -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>
|
||||
@ -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}}
|
||||
|
||||
@ -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'>
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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")
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
});
|
||||
@ -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;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@ -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>`);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ export default Ember.Component.extend({
|
||||
|
||||
@on('didInsertElement')
|
||||
_focusOnTitle() {
|
||||
if (!this.capabilities.touch) {
|
||||
if (!this.capabilities.isIOS) {
|
||||
this.$('input').putCursorAtEnd();
|
||||
}
|
||||
},
|
||||
|
||||
@ -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');
|
||||
}
|
||||
});
|
||||
@ -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;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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})
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
@ -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 {
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -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;
|
||||
},
|
||||
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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')
|
||||
|
||||
});
|
||||
|
||||
@ -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')
|
||||
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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(){
|
||||
|
||||
@ -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' }),
|
||||
]
|
||||
});
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
import IndexController from 'discourse/controllers/group/index';
|
||||
|
||||
export default IndexController.extend({type: 'mentions'});
|
||||
@ -0,0 +1,3 @@
|
||||
import IndexController from 'discourse/controllers/group/index';
|
||||
|
||||
export default IndexController.extend({type: 'topics'});
|
||||
@ -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 {
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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}));
|
||||
},
|
||||
|
||||
|
||||
@ -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];
|
||||
}
|
||||
|
||||
@ -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") {
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
export var CANCELLED_STATUS = "__CANCELLED";
|
||||
|
||||
const allowedLettersRegex = /[\s\t\[\{\(]/;
|
||||
const allowedLettersRegex = /[\s\t\[\{\(\/]/;
|
||||
|
||||
var keys = {
|
||||
backSpace: 8,
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -97,7 +97,7 @@ export default {
|
||||
},
|
||||
|
||||
quoteReply() {
|
||||
this._replyToPost();
|
||||
this.sendToSelectedPost("replyToPost");
|
||||
// lazy but should work for now
|
||||
setTimeout(function() {
|
||||
$('.d-editor .quote').click();
|
||||
|
||||
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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');
|
||||
},
|
||||
});
|
||||
@ -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});
|
||||
});
|
||||
|
||||
|
||||
@ -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));
|
||||
});
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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;
|
||||
@ -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;
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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 = [];
|
||||
@ -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
Reference in New Issue
Block a user