From 0efeb8d1df956db775d4c343da615ca2ee005276 Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 15 May 2019 21:25:53 +0200 Subject: [PATCH 01/72] remove electron, docker --- Dockerfile | 29 ----------------------------- app.js | 33 --------------------------------- electron-windows.md | 17 ----------------- package.json | 6 +++--- 4 files changed, 3 insertions(+), 82 deletions(-) delete mode 100644 Dockerfile delete mode 100644 app.js delete mode 100644 electron-windows.md diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a614e97..0000000 --- a/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM spacedeck/docker-baseimage:latest -ENV NODE_ENV production - -RUN mkdir -p /usr/src/app -WORKDIR /usr/src/app - -COPY package.json /usr/src/app/ -RUN npm install -RUN npm install gulp-rev-replace gulp-clean gulp-fingerprint gulp-rev gulp-rev-all gulp-rev-replace -RUN npm install -g --save-dev gulp - -COPY app.js Dockerfile Gulpfile.js LICENSE /usr/src/app/ -COPY config /usr/src/app/config -COPY helpers /usr/src/app/helpers -COPY locales /usr/src/app/locales -COPY middlewares /usr/src/app/middlewares -COPY models /usr/src/app/models -COPY public /usr/src/app/public -COPY routes /usr/src/app/routes -COPY styles /usr/src/app/styles -COPY views /usr/src/app/views - -RUN gulp all -RUN npm cache clean - -CMD [ "node", "app.js" ] - -EXPOSE 9666 - diff --git a/app.js b/app.js deleted file mode 100644 index 73d7fd5..0000000 --- a/app.js +++ /dev/null @@ -1,33 +0,0 @@ -const spacedeck = require('./spacedeck') - -const electron = require('electron') -const electronApp = electron.app -const BrowserWindow = electron.BrowserWindow -let mainWindow - -function createWindow () { - mainWindow = new BrowserWindow({width: 1200, height: 700}) - mainWindow.loadURL("http://localhost:9666") - mainWindow.on('closed', function () { - mainWindow = null - }) -} - -electronApp.on('ready', createWindow) - -// Quit when all windows are closed. -electronApp.on('window-all-closed', function () { - // On OS X it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - electronApp.quit() - } -}) - -electronApp.on('activate', function () { - // On OS X it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) diff --git a/electron-windows.md b/electron-windows.md deleted file mode 100644 index de6a870..0000000 --- a/electron-windows.md +++ /dev/null @@ -1,17 +0,0 @@ - -# Windows Electron Build - -sqlite3 needs to be manually built for the iojs version that electron ships. The following code assumes electron v1.8.4. - -```` -npm -g install windows-build-tools - -cd node_modules\sqlite3 - -node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.8-win32-x64 - -node-gyp rebuild --target=1.8.4 --target_platform=win32 --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.8-win32-x64 --msvs_version=2015 - -cd ..\.. -```` - diff --git a/package.json b/package.json index 34bbd9f..56007bb 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,10 @@ "version": "1.0.0", "private": true, "scripts": { - "start": "electron ." + "start": "node spacedeck.js" }, "engines": { - "node": ">=7.8.0" + "node": ">=10.0.0" }, "dependencies": { "archiver": "1.3.0", @@ -17,12 +17,12 @@ "cheerio": "0.22.0", "config": "1.25.1", "cookie-parser": "~1.4.3", - "electron": "^1.8.4", "execSync": "latest", "express": "~4.13.0", "file-type": "^7.6.0", "glob": "7.1.1", "gm": "1.23.0", + "gulp": "^4.0.2", "helmet": "^3.5.0", "i18n-2": "0.6.3", "log-timestamp": "latest", -- GitLab From db849bcb202dc1364fd044248360f466813685cf Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 15 May 2019 21:29:34 +0200 Subject: [PATCH 02/72] fix most vulns via npm audit fix --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 56007bb..1ce6692 100644 --- a/package.json +++ b/package.json @@ -13,32 +13,32 @@ "async": "2.3.0", "basic-auth": "1.1.0", "bcryptjs": "2.4.3", - "body-parser": "~1.17.1", + "body-parser": "^1.19.0", "cheerio": "0.22.0", "config": "1.25.1", "cookie-parser": "~1.4.3", "execSync": "latest", - "express": "~4.13.0", + "express": "^4.16.4", "file-type": "^7.6.0", "glob": "7.1.1", - "gm": "1.23.0", + "gm": "^1.23.1", "gulp": "^4.0.2", "helmet": "^3.5.0", "i18n-2": "0.6.3", "log-timestamp": "latest", "mock-aws-s3": "^2.6.0", "moment": "^2.19.3", - "morgan": "1.8.1", + "morgan": "^1.9.1", "node-phantom-simple": "2.2.4", "nodemailer": "^4.6.7", - "phantomjs-prebuilt": "2.1.14", + "phantomjs-prebuilt": "^2.1.16", "read-chunk": "^2.1.0", - "request": "2.81.0", + "request": "^2.88.0", "sanitize-html": "^1.11.1", "sequelize": "^4.37.6", "serve-favicon": "~2.4.2", "serve-static": "^1.13.1", - "slug": "0.9.1", + "slug": "^1.1.0", "sqlite3": "^4.0.0", "swig": "1.4.2", "umzug": "^2.1.0", -- GitLab From 58aa3fc41fa4c8a68c65fb3991bce6d6af03f7fc Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 15 May 2019 21:35:29 +0200 Subject: [PATCH 03/72] fix gulpfile to signal completion, clean up a bit --- Gulpfile.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Gulpfile.js b/Gulpfile.js index ee34f4d..f20bf7e 100644 --- a/Gulpfile.js +++ b/Gulpfile.js @@ -1,13 +1,13 @@ -var gulp = require('gulp'); -var sass = require('gulp-sass'); -var concat = require('gulp-concat'); +const gulp = require('gulp') +const sass = require('gulp-sass') +const concat = require('gulp-concat') -gulp.task('styles', function() { +gulp.task('styles', function(done) { gulp.src('styles/**/*.scss') .pipe(sass({ errLogToConsole: true })) .pipe(gulp.dest('./public/stylesheets/')) - .pipe(concat('style.css')); -}); - + .pipe(concat('style.css')) + done() +}) -- GitLab From 8ba37a11d6f301068c75c2b902089492324a0c8a Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 15 May 2019 22:36:53 +0200 Subject: [PATCH 04/72] purge IE --- views/spacedeck.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/views/spacedeck.html b/views/spacedeck.html index bfaee5b..daf54d5 100644 --- a/views/spacedeck.html +++ b/views/spacedeck.html @@ -74,10 +74,6 @@ - - {% include "./partials/login.html" %} {% include "./partials/space.html" %} {% include "./partials/folders.html" %} @@ -91,7 +87,6 @@ {% include "./partials/modal/support.html" %} {% include "./partials/modal/login.html" %} {% include "./partials/modal/pdfoptions.html" %} - @@ -53,7 +50,7 @@

[[ __("contact") ]] - © 2011–2018 The Spacedeck Open Developers https://github.com/spacedeck/spacedeck-open + © 2011–2020 The Spacedeck Open Developers https://github.com/spacedeck/spacedeck-open

diff --git a/views/partials/folders.html b/views/partials/folders.html index 79eaceb..2106a8e 100644 --- a/views/partials/folders.html +++ b/views/partials/folders.html @@ -5,11 +5,19 @@ - -
+ +
+ - - - - +
- +
@@ -78,7 +78,7 @@

[[__("access_current_members")]]

- + @@ -91,7 +91,7 @@
Icon Email / Name Role {{member.user.email}} {{member.email_invited}} - {{member.user.nickname}} + {{member.user.nickname}} (pending) @@ -113,7 +113,7 @@

- +
diff --git a/views/partials/space.html b/views/partials/space.html index 3b7482f..86ef010 100644 --- a/views/partials/space.html +++ b/views/partials/space.html @@ -1,131 +1,3 @@ - - - -
-
- -
- - - - - - - - - - - - {{active_space.name}} - - {{active_space.name}} - - - - - -
-
- - [[__("offline")]] -
-
@@ -140,14 +12,8 @@ - -
- -
-
+
@@ -158,17 +24,13 @@
- - -
+
{% include "./tool/toolbar-elements.html" %} @@ -206,7 +68,7 @@
-
+
{{a.view.link_caption}} -
@@ -463,7 +324,7 @@
-
+ -
- -
- - -
diff --git a/views/partials/tool/color.html b/views/partials/tool/color.html index a4ee018..ced83e7 100644 --- a/views/partials/tool/color.html +++ b/views/partials/tool/color.html @@ -126,19 +126,11 @@ - + px
- -
- - - - - -
+
diff --git a/views/partials/tool/toolbar-elements.html b/views/partials/tool/toolbar-elements.html index c08d8e1..d6080f6 100644 --- a/views/partials/tool/toolbar-elements.html +++ b/views/partials/tool/toolbar-elements.html @@ -1,21 +1,24 @@ -
- -
- - + -
)mLd)e$2^c}Ng3XZ3y66Z`d8iM#RlZb8xrN4iVH@wIc`Oahc^{sD>O&vXp99 zL@>caOcYEy-ZEG#FQ@HkY4=Jld^GSRn!!+Rt)Q0c*u~qCiAyCUu)0G^tgDNvK(HQy zg%XSSyd%)iEm{TFg?Zjtys=>hhF2Gvd~q8ZhK34$Q)Br&1MWI&mpe-a zAO}{EO$hU9BlWgnig<6=lo6(rDgX?Q^WbZ+;?C{BYw(ve`<#sN2`H29sslHGOr~!~ z)bYYTfY4;#4@$~W(u_EKD1`S!6KG+)BqUoCQwBn$WLO>t`AxPvgHQLOI>7f82(nRI zHdTNUjbQ?q0zZ^HcjQw9p-_FI*T<5d7m`JX5UC$>_WXy%` z&K>5gyoruAZ>1}fQjERe#I@|v7f$9Y!xF)!6Q@SmDsvSOqrxWe-3L+u+jMa@3B6l>2AJZhblMHXARp%v@mTIc1)o+78 zYYLyP_#x}+_FLF3WsU$^vPjfra0OK8xTKa_zSEHD)d{&1;%daDf@v9K{*vm=vCmnh zOOmR4S7@nH6c(IcDizAv4Vb=;F)}m6@i3?eFm5%1iNcmxL*X4Ce) z2R@kt3U)eYyq+V5Q#Gx?Z=v|{SC+0?;t9N}Y(HxT(q`R<2Obdrc5MBDtae+bMiXf6eLk+w?04*+jNrK6u+RGxO{8i3T1fAF_Z^ ziFlBS@o%+&8@bjADX5^>i3|!IchliI@tHXy0us4TwtWt@6ZYhwxJCVwoA&nkTjlgt z*D%dHB<1*k;{I4}if<|y1I4V-6A1HO3nOuKWzZesywu)S~D(;7GM1Y=2=^jwQn+=eL+3!FB zC7*%WM5{t89L)(DRowETKP|-zKp}CoTj4U9z`$R*$X{pmYW`>`Z+~4-YDUD@v33VA zFR3pPL5z#@8H|k5N(O1SDK%TOD`->{J3f^cCBg{f$4SNG1OzkjYS0G+-2oui)UU)jhCx``*qDioX6ow+UXqv?)(7I(|MIL4&3lugF3<4j*!S{h4#v zccc80jM9(jg%}w!i^e5aaW@yF5krJ`JG>GoP6lv5motZ=K0*%oL&8W|bx~u#ZRI;c zo!-&C>%kpH!bk`tN2=JFgpHWBX>R35Kvtg|dOY}gg~kovu+Az_fl9=BoFELGd6U41Z3sV1y)u z>*bHf#R1+npvtIMP}*4d_K`JnH4FlQUp)AdR87n+{Ozo0@aEuEA9E`BJ5&~wcq2g5B+PLq7g<{l_9 zy(Rs@H~Wjc@N=$zv1KSmt?%^?-!mD<^OQHFh*DdCR8hU*5cqE|8mO>f z-1|>Rixr(u-%g<*a6ui6fZk$9KhW|W0PaU9Q_lWSLai9wYoG!^tq6{8Ny?V3)YMKQ z{GsP@6Jd{3@l`7AF8J?oF-ft{yRwvh0dy=##or{?YIqJsnSf+0t>-w3aRlthNF`xY zT!PvLQjZq9yH0I`!z{By?g7(h^y3#y$4hlm{nHF@YRWTT3_lL(g-^Ny`lFl^n55mu zjhr};LtN}@r>?g{Ar1%8aOtHvL6A8@y=Biu031o#bp_w@guAbVcVqQNab7lXF5&RJ zM&C2$^7C!+m+ip6nuTvS{(ItoNCql+ZFYnR<+b5_?Bd4rDn0S!KI*dGje z8hjXAe^Pl<5c1+Dc3q5f@!#7%!DakC>HUj0{$uiDmw2jbY7hUZ>i#3za;gwGEnjZp zgk&4GI{(0~hlQ-(RCoVV;-C`Uh+n_OZdqo?dZg0j<5h{SGk{dkMN8KDcDTB|a`c(Y ze?p)Av@j6XqL1H2Ylp@1{ioaS5;r5!K#ADUQPg@)D*yJ2v6j{fyWw}e^lnp;_x%rP z%(e`4WDd3G$WsbQ>2H1w^rI zd>1}$@95Z?J9J`Mb@TM>_im-R|24ZyXK}mI(O?BY`6oRwJs~o-`9jwL-MgHm^IUav zbL+W0Q0P$B7=pgO)Qd^7xi+j`ZH*T8Euh40-D=7s5e?h@r$!M-s#R4z#1a0t`KvFW zKGs^u8ZGr?Kw5{h~# zs!Ixh?0t8y(VTWJsH+L0&yqslRBTJB%YQFE6)}~Q?XQ6938+3!`$^4I8e^?8ouaJL zkl+9FD9qRkGa3=<>>5wBeK2g>QBhj$&_yzbt@z}E{f+6nR~j}s&rJ!P+(g@YsKXyG zR!)8=rbX#nCVU2)UV3Jz0V#S^SgApN978~Baeu@ViW7aSEX)v>4p6GlaIRN>At1LQ ze(HthnCRzOt=|mC+xBd7x-Ga==Q9lczWDcn-&tfJ_%%g7`4q3@TuJI2Da_W3)-1x) zKhRx9HMtL)Zklt%ddp()IojtS8jre0v?1jA2i)N8q4E;w;&VCY5sfr6 zSs&hI*&gX>5y{X9=$yRkj7Rm5r~L)k^ZQO?zJLEY8-`#!wUT7xZEKrTSdp560s?c& zfdqosvMm}qw*M`X;8jw-p1wX^rz3s^GnM589%vYRZuH0Yyrj|O?x)30mFd+BpOWLo zYoZ~?10RlP%u*r`xO@GEdp*|A7K~T7uYG&=CK(#UH?I#O@Xb9IxG@!1h=2s)Uk2Pc zJe|TdleTa8S~@-cL6s;#IOz%9ikH~fO=>r1q+95|kBLptD1;>Z$ z3dV$jkB{F&ewkA&7>Slqw+K|W`Mh#CHSUbA!?nowTUWJn4lQ{~e?RwJ9Hu?Lc{cBJ z)wfNCUUa`+6sBhM@)7ASD;XS}ORJ%$mJlzrn1czq^m?-od~mova38lamFrQPhJTKA zSN*&{jbz|e}X~@q4^vF~ZI!k#ajMl&!JRYl?ntUW)kz=*kh) zAWsT_8q@{xq77JAG4gRGWxu4EwIeO3IQ1RlWJ&cfS;X!fewjr#Hc0TcEmaW1^WJAu zRn=g+?R#VEs>&^wMmVu^EpvrHmXLo#5(_{Vlyg5Y?|RQV;AvhgR^o>59>Y>bQB@;F z>b)~dI&Ge8#|!N9T_BK8HlYk1?oV4(35A^enMV=0%Ibc{7$BI>a zDdviPMUT#~rf9(I~c=~VI z!S`+dFt(88^|=b&t9{T(zuSCWQipqVYNE1RM%G?^1GKGb=Tt$2 z&z6**GdD!1IYG@>F2Pq>=eEq4Ze~}psWu$@ z79x@D4~#OOYiBBdPXex~BHMIG*nU4V;5=sU7p4<8Jx!1qp9@!$to#hhuZd@+S_x5D zow2pnu;6sMD`)xv8WT|*9+4r9RFA8*;IRyvgq7@bYJRVYxg^Vq9> z9WncvmJ)Vw;My7bNk)LNrIGAyK?hWUYb#n3zpJTdDGu;<c1 z?2TC0te=BbG97_T2rdkGi688F7#ZOWzvD#|u{`7Eu#u7`{Ng>!lbW==;Ib4;iL97` z-;vENqw$jqqRo?(@0cdLe0n_dy)DH8y>kr}oGAj{u)P<7uEGC;Gf<<;e8( zquMO_>qo4|6Txz0cfVj{rlce_S13(n>jj0&*3SKX_a*l0`laLKiP zW4wOTD)#bUiTm^m=jqMsU(;vS;Mcp5!vEG+*W>Eh-hVgB{gKRWhTaF9HGAKQfVIBI zwL7u38>r94ipPZ8$r4}JUDv+o*(UrIAJ2mCI{o%?Y~Xr;>s*~}alE6(th&v~vM=0Jcb2vaf%DW-$cU;MTuK5nP z_`YDcfDmjfPUk=I^3A5Py`-A814@%149z#L-1#-|Ab3=2shX68W{2DPIq5Z&x&*yu zHg0>?sl$2LKPVh^AE$aHH;>|4N2JPoqKo z;@KVCK8mpe2FM|Je=Uif4n3N8aeLiF?0|08bnn%`HGMw!JB+Vn)_fH*QNi#M-o;{C zeyNnM&Q)fEIGqq>rDn=xO=F@FtrvLSFkyHSo-xBC==N15 zR@GJ-m^? z$ISHl-&^V`7jzL3kbWxF$xFczsibU(+<*i+)k&_O47cP(|DlAI;+mm>xt45ok$`-~ zM#5LzW(a;jl}?pg2~x5S(N~o2qtsJ*_S?x5!O8eF+I?DPC?x;lNb#2`0#h8Qd22`G zGqI6<(}>J^=@h&OXZl(B9oy*!3}XG{rOmr!z!pmw&}&ZrsBurvQ`}r7#gXpF1X1UX zcrS~kKh#wT%OGxE!1T=0tJ=g~(_$3ItV+Jf@)pZX7f9mvyQC5!H7?O*pr%n=#e?AHF9Sg(2Q z{9onDzJAI6)cf)2?dw0&SBL)_NkN%j&vVe(mo_Z4 z0+CZL=8b645yK3JfxmWTMC9mgPy#*8gsL>IAu2;$*-j9O#HT-kyA6b{+N=gi6dGI_ zK$!l3_hX73g$*L5S{&9eV=C>3nuJ_oD~8c79k$RYFmft~9i%8>>wZZEn!j39Cj>1N z?c=>IOVO*_-Q!9?>>P_K79TnenoQHQx(pqgB^nnZUd{a}o+7vS<_f&F=!KC19|%6b zeSVLb0QnBU>N6iT?=FR6$?<1!r)EgzEW(5m2$ARXa=m4ZKelp9c>_S*$jV4&ve@YA~j6!|>6U`wGk#wjfdVLCURq=Jvx20*+9H z%`cx&T5S)B9g=hyDe}P@^|veC0ye;+$}dxyP-8P=S&m4~VRpf<${vCJ<`f4k@;bQ@ zk~w6%W#Qq(dDS3V+~0~qY)pHEX0pu&qr2T_vCnVj(r(ES3=uHqQ@wgCV}lBdgd@F? z1#v5=q9{9Lz=pk(?=+VEM7YVD;|1EOq$}y!F{{O&A`CxWb}%Ur4%51;UYhhGtr5Wc za>8Y;X+4Aq1tHE1n%_$Vq77%PEcYi^r$_~l-D-#!e7D-@&RVtNH60BJSfaU=&U;&b z*(VTFMsOP8(_rq{MZkWeb`$n?pmml)$O#FUuWcm;-?&AXf81JdmSl;Qs(`Ws9eC|I zW^i!*f!TY`JKGFr%Z#!HsTwi87GAP*>hApq?HKYYSaPvn3|SAenUUAxO&AB5wzU|r z^E;pyYf1~`B@b^9>UIIH&|X_v%+TOt=62412o4c09_Q(3y{z)UcOZ*nA4m0j&NQXH z$pN~|Q34%#jNXg|>XwxN&+&mhn%C{lpJwqvUGQ?V6vFa9jPrkI&`uxq3#Wm@|Bfzr zn+5QN`aYC;Lq2#%{(ivpN%p^*^y;{A;w8j?sep-zmoe zr}={j;2VGaeO&!}q=xswv${b<)c3zoZI?6QEgiMz8MWutz{bLTuZIJg=#!`c`$33d z=vTn`zy%MWG&0GCmy1%NoFwu=QV!q1?mIQ<6Po$^$5fCAv#(>Co;&pwft%KJ%dt<| ze)0D=Su6RFe0M-)!2^t&rPRycJT4ZU?eDXO+?%Bi!y0o}y~CXh zqlP`6k1=g1otEEzTcl9nlG6Ckhyt=&ZTE=IIGe(G24gPcRv;EWTnBkCQ|j1MTrQ+l zYQQGF6V0{vdahEG=OP1rmm!`5k&XGq!KTocDReupIAU*HbL~LQ? zz-Uo|0GS7@SQJr6$4*omJ7@jRPtdAiQC6{gxkjA7mch<-EGS+ z|3VZ~_H1AFI4PLmaSr1g1c78Y*4sEsRf$?{;o@2?9SNIkSiqPzb?s^t4dl^v|xV8Do zDMyxI<<&51on&VYnRi=uuXBMXM8wt_?S%1Tv-{r)j`7HmS=(YJuD>sfeLE}Z1vuLI zJ(UDKVOEg_^6(*R)e)A*2%7WPncZ5Z(M}sAc(7GAx{}+Y@Jr7t^*XSJ3DoDbq0}%t zl=XE}rP(5DVAH!3HdOJM$yA-(&F^S)8UvB(FiG5dY{PZPw6~WS*g%}K%KDN4&GZOC z*6gy{==YzMR1x;@YD>!$$T(8095t?#lZu#lg9hr^zwEk8?8j()E*o#c)pVnnRrD+cSDd`Az199@s zzdNYU{QAV~ZH;Rp$0185S03Ef&!nvZKzJcd1bM-SgIBDiN)GatDoMm|7JzEnIpC3z z-UblTp3J(-;;m>OC$2Q^+wYl5YM2Pd7?she9F+*`O!i~S9z{h9PW)`uW?j3dk3N{& zk?^G7RNjBV)&GL2E?#d@pMOlkUQOr3X9u55pXXnrK88HA8g*ALo>l>X4REQ1wMq6q zo&(FMCyXzX6I-k9;wR0#RDli9Xz1#_$umoEpba^Y{jHP~n_q2@^O_;10b016M(Xw4 zNkwJJx4EnH^B7R(>PsB-2D4B98p&z&@FEmU+{6J#$2QbTX)YQe?a?h!+w9_wwa3}k zeTy;}-Yl)9+X0UM@eU{mxkc8tgM#-TW(#U+*mdUvq_W#XdPgdDos8pwJvQaBGBPOMcwb47CiR<{T=yTsWP>kX$a9K3N_^I~Eq#zcS zGjhk@w-w?dS9NH&S|3@xb%ouNz^7*q_lGTmQ!%i2h!1``Ae?JVMqNZ{+L2}B2$}`7bQI3KSABl&II2iY=OO7J? z@2`h>OoE@@$tj_ht-G{$_`zOpyI%!Angl1E`8?e|w*F?`u6JgF)#LXEJ4++ z+6ANH`hqPm;{@X2{Tq6~#MDDuFv4=55EV#|< zzj**E^MF8byE4z|-!HV>g{M`c3GPX`{Uc7b;cU)jf60xy=|n}sCYyBU!&azKl!Y`W z?q;^sVd_&c7m?oyGaSm5vhn#)LiabB#)w~ckS7HXJ^Pv7x1_nLNUt}u4f5;N2(11@ zEOi_I!^iGckW-1b&FfEzrR}8Qlx**>h)1QW{y{5NN(xq_5`G~H`BCB|YA%qL6$(z5 zc&bt_^PstaqFb^rDGZLkr^Ic5*iSZ>(Uuyr0EekSK>bcrvpPczJW{Mmu|KzJl*-a~0P8fMf>)c(CvlTiuvl0iY72i1~f% zg4VgJC)J#RJ$rw4*^ukh_>2LJ)POXBPs(M+qS9e06ZgZOpX4KoH7~>qT*))-XZ5(n zJm^w;$M#G<#M(e$_F8}2DPZ{@3@-JL2yygsIf z57Wq{?Cm3=E_LM>SaUJ9jaN~21w|uz!vT!BS%QC@>(%wd{2mBERp5!_YjQOpLFdoi z)#X-jl1)L#HJ3^!Lb;y^)qGNv#55=lK<7$d)lYcvNwnMw1O!+vI0~8d2_#+K@o|G> znB5Vgw|C>!I9twdPx#@BU1x6trr*>|0WB=yge!2@R)Pn4ah^82pBPME_I$3Hzu)9G zDQ9ddco(>lnNx8YkEW}M?U(LwA^<=D*6m*i*5s}7?6gO)nc zoBPbWEIQs%BqI)9#rm}sjU;evDFD<6w6)D>vcgUZOP47xo_>j7vbri+VzLbeKwh`wWklN)mU#9WpQ6Dt)qhHO0?00Gp{Ryq`p#!g zsH+7<5ivDQC3~_gRyMz!G|G$YcY?5!8c|djs)#O}tX-uPcTyzlxULgR?W=UAPj3&t zuGb|JD=IFVHOb)Ms%4-<8~QgRIKn*v5Lyycl-xeeurv6Ooz|9A`_r8HkkRQ+Mw2x~ z`PjHQsyGV+x`VHCDd}d9p+*=eiJ!2WN)J$M_{@D}tnz@K#yw;qbyz8 z+)#YH=-x+cYYIE?pNcehqrpg8Hhx&0u=$7m4s^}kWd;xC*dQZv@RcoZyF@}L%qJox z8x~J5?2zb$Xge*0Yot36HxF2hO0cQ`+GUmNwsi7CW? z%{CW6_>^=q-q^}c`;TwGOQ%?CEQ|z;ZGVMt*K_ZoXD6BUAl~>KPP9ne8o#waxCo}B^$Pm~#zSY>7 z?P!cIeibgVS52{yT?9fvypdW8f^S3pa?wVWy|A8t4&DVsx$OnsQQC=I-CXBJ^P9T0 ziG5o=dHCmJ$}^EBK-#?LA2Oz9@`41H?8JQn!}+#V$hoTor&MYpM#N$E3M?fa60<`O zn_(?+DYP)>L!XJo>$u0xGh|n}(fz_#rQm-L6Mm1kss(v~#WUeOYpXV+`};)iT?5$1xpoD|#~L>gNIbhs7cRo^XUhV=EX`2x66UK4*^736gH@rjB__ zW5M?jzk+IW$#(SW6iw-n8ziH?3?6H-^y@D|q7p3Gi~&t&3XKsy<)e`ucGV|*<99pi z14UE5QiR+LOB&jE*X=C}&UBnm15QFu^s*~DFLc*uq$MoAkH5L_Gm*h`I_lPxf_ltV zl-h9y>(FCd9Dw^*qYQfr57iPkDFhP=$vMqR1S5=1sSgUaltgtb?JP90CO;&cZZ82_ zVGa~>M1Q%{2ScreH1-g{m9PjqQ3jMPq0hWaF}|$|-YU$<4w!;STAExP8_JOsx_}=N zZB5!4UFxs^ph3it0S}-VrIhJUqNm^YHcz8@O?wo2jim=UgUKrTbHqL-!(cOt#3#OzfKb?iBC1!Ns_UgwK zGrWYjzI~cD{@=>D>3*7cC2%o$PAYt!EWFrZ6t_`L1#X@nGw^9oHElF+1>T8HKfn|2 z6-gG~sP8ZoG;#!~e~6!>CRAk&v&PrO9r$xbpLeqQT?NM`5o~-cVX~9 zWH^1Qd$aTGCk-srfN0!Kk)IDsNtJC>84W|Fkw(s~u|K0q3m)F^pI2Yh>B#r)G~nmU ztwV)f4_UO6K}PfMt6%)}FPV>zJ}V-AQnk(;yNoC8xEu|?eTV2r7>%)YNJ-9rj zib(2BdG%7t8y8V+Qg_9gI%7?X5}nBJ?6Uk?+q(_+O8Eqk)$cK_6#Ep~KL)EOni4Hz zH?;biD2B~50f_Ul*dqepPpVo{mqT)CICdcF^i#V9-;K(SG`=5LeKI6F)4!C^sWH=z zMb-}cYd|`{WV=iisDzDZ?)wGbZ@sxn+uf8Y6n_L#qh530DaEIEX6M77u#GAA?yoWH z6u0Krh4sWtKzPQ^B8~<+lKlYL5Bpy;>*S4JKq%X6OX4I;kmDzq9%ARA6mRfl}L8Q)0yC5Jp6t=fZCA z_Ln{K+ZL7%sCnaRNu7Bv>ig*ve{BAbZOW+(#7h+eqynN{mIV{nb(MBP9Z<%|)?@Dg z?@rNZMpUx1oUC^^{e-?R*UUb5H~^Q6#H~l4`Xc(D_gj^LkF5k*(ewRA>Yr~O$r*e3 zkm~BbO6B#|oc(-jeAqhQ<#!b|{qUQDIFq4a@mk=BSs%~RoI>vB%bsD>>#N?-MPvOg9;YG-+>1)CI)1EFoC}G<1of5qK<%*l%|1mxU zP{G98{~c`ydkQ8B!Prmhd;Hj;@N4ib+vSCWm7Ic*f3vh-O zo-f*nZ@+A&>aKTh<8#ZBCQK!aBC{AhP%_(Waa$%vWw`OV`?X0s92AO|9(p_e6WzC? z$;x3sNpPDos*f#U$?_}1t-hkWx>t~$G!`Am#>%p^9w%M*(*j2LSv4->XGQvfe+eGL zFVAMVSRE${`=v2hmL;E+s*r!!*|zAqZx3z=;a%a&-u-H+Od_CRsJ-2xEsU<^c^p)S(*aD}LJ*yTS6`LXW#A{#zVR)# zYQE~&jKkb@ukVUKbyKh7Q`3e2$pl^%rjy%3^ zD_=!o?XrGh+m60G!hGm3tWl8&KlaY{BKCAIQxEU&{!pa_$m1zD7_}%7)Hj4z1s zSduplBcyne@K06M)m?q|$#Qk)R{466IxN&K1He}8`1=_E?nRv1?;BS~kmkv(NvXEq zyW*3%rRY>?ewhYBecPikR7SVR7F=Qw_z+X-)u$<-Wl>FU<4=pCe=a_l&3YEPQAOgH z4ymLhQ1+1Q(#xH;08K=s&v_~p@J@^W(|nR5V|+KPABK#_Xg;~`^{vL^IKm(gLZbHD! zGWWweK?x!}KnFCKhDph{ohx#qM_AjXjlei@U`kB7(%C<-gFj1q4nZ9maql~cMy3f( z6id1tTd0>HQa>@Jb+iSt@@qo|p^ZGE2=1q=)-7srt)M5yi*fM4 zXc-a{TgaF1fBOZGN=;hvFn+vvqfm;}rp&8zM0u_ednv#(1~J^3L{s?__k2H9>lo2% zJTFY=bEp856f|->9`n173S%QY_bj;6f4a+&$9nfcbY0_Og#xs7SxLSHKZIopdK-u9 z5i14fraYsgSx#!SAoXGqA*bf!u7_bEi=EEo_oBgM2Gg!_z6m7#UllZ`IGg}f&O;or zVd&z7)4&F{=cYQGWAMF>D506mBF7iov$WvcyM8R}E}Ma4gQH)M!aP+pc2k-oZog`( zicJ-lepKL-28nfE?hDL!64pM%$V(@bfdG*g#&X_DWKma-C=ZZaGaW}@vppd`V*4sV zIDRhKV=aF*wdRHMd#7ucYzxum5DbpF57V%3|yp zZNox3$*{ZxdF_kj)tLsHuWycbixi#*2BUb{yv_D&yRq@V2s|&myK#SRtE{=$_%%r$ ze)WE_;RULXgk*6MdmvyVFi>?&DD8||SE_(?)n2u0|9Z3D^7L+)V0Sdbtv(-W+D28% zHrqg;F;YMk5ZF7FSZM*iOE&<}J7I0qN z?us8%MTSE9m%iBS?A$yrkJ#|=_O|+OcCI_ldyTgaqz=U z)LGSD#hDHHnLB+qz+$S6R3!!;4?d0QmekRd{|9rO-0=7}y?S7gXImZ%N3{?Ssm))ajjGkFSmuW(kXMg-?ivoU0 znR%4iIQMX1XnwpK)&13&#^ ztL9Jhxj94vZy9)hna1YuY!e{!= z$+c~|`fzn(61S2!dD@qV6T%y3U9p)~H4Kz6aI%E+yQ8Ogb^5#b3yX?=K14r$w4wWklCFR7>uhV^0Y9A&w}^uxkiBvVcDl=1E#24?9i1h7E{ z-w!5D`Wk?tW!zX!dd^8-A%l80A1UGIOV`GOue$M8O-bLqA)ape^E!v;_a{?V9`UtA zRq#`#7nNjV8r^EVVIh{}L*in1`*R}32T&xML=NaHOo}DSBsX-aE48Uq{i3k!EA`<5 zVVA1!V)tNSv^|J*XY6tJYEjJcfxHV1422)`inLeBr^sa`3ik4aF*X(Dy0SWQErN#e zXZ;~Dxjjr&#h;&1#!G@|sH4#Tsbs1rVv4;IO$-Nwcio1(If2BS^GqO+QxMxqV}bQ% zsmjJMQO*b?GsqcP@6T8N){cANN6KEQIxxm+tdWCsUliSJ7DzbbWYFSpmE(#$BQ_9X zQ^iYwj!AvE2}cJ$kKjhtop#NK0fAB&5mnAV&CN-@L-kvbI^h8%#c@^1d$IBpmf>UgpC8u0I6phB%NTV~sy|}JreZy(7&X6J^XkgTI7)=x?i=7%P zONYDJeg8sIb3KmfZ7gEIR+t3T@^rF0_v`mIl^VAABx(HE@e7(1{2*`T0+5AzX2#?t z;+mTf-W1xK%9gN&!lbJ*dK*xzV4^_vQH&~UVy@$CKj^mH=M~{ z<&}5lXM*|pIC86*=cK!O14>{mwUA|rV(mw`Ej@LfAvr^dN85=vYD>R_m3zKGy})P zt!F^O>BMnfx7^tDjdtI*!nj_Y9IPMmw~1eia-ukfjCan4YND9jom=nEs1)%omU&p8 zK5cxXAajg0Q+TwuuU)EIT0WEpvCx-pG@bfa6FlNPkg##{7ddXMB{9_*4b&-VJ4>@HRz}~rtWrz&b0E$d^a7u zT)#84QVjUsOz#w-0&!TV8jCL{fJsyoV^3uB8&bj1OklM?en#T!Uac@GsjM26Sy!nqT7z`VX`>NDLg?>R zs$CueR!6WV?C&09T?7^8U>*_?jKd?CcLRo-hWTJJ-4I zUmC&K1d1aRZ2VfDA2@zdajR;~bGE0_b`(b0*>rY&rj<=ulT01Os${Wy|F$Bg1F9yH ztfB0F@k7N&y^$5D8!E-e&6F27dNJ%BIm&`M!Pu2Af5Dj#E((v4`icv%70wYc;TmQe z*7K4Bg;rs?UU|HqXRfJEmKVzln6i+u1?2x*!ZLXYCi4P2V=drlC~I66*;7*@AXA6r zK>M2oqSRMCYV$VD*uO6_D+uObvGA+E6gxs&5fGk%%baayU4=ZFPkuZ=-c!kj-iG*< zQ`FO0IrK8G6U7e`?K$p+bp;GA=zu5vZF*55hoV6Qm>Fb;Xyo?138`y_tnI#oA0?Ji z7qSvsRA$Dv*>{GJ(a%gL#i=a6>q8ESRLZ`IVmp<_<>zyp@O!$jt9BR2^AWf|UHtkY!ygeE) z-iq{!U;<{>{h|9KvG08_!~g0X^zH4AI2bLBgD*4}B<*iay@{zvp)Qq-;>LG${#=zCpl80ZTv!ukH-Wkq4+U+o6yIUA!cGuien6=v5hJq(400&m>aFZ&<@N}V-|>e z_?c!D;V;lk?8-j{4Kwa6hsj_Ua%u(r=rtdzg}!D9qDr({9_Tb^Rf^2lPoW=?p4B2%$jIv4GD@#)EURaMErItUp2 z|FZykDP&~K&!SIX*AfQeJC>n8A)q($Pui72G76^DtnG%B)FljNlr;o&ZCKk0R(&V` z-F(2KIp#6MEuwYZ@KM^8L;?`PpTmg44ce7k=?7e`0$(LK0OW3*J-fETMV}vt1hla5 zeV)=@Ca6{L?x#^xJv)lT+yu|5wHmW$>r@B~K266&C6U zA>aEiDJ>rnaN^|T@SkNowb4hzREq1aYQWRK9;15^*a)xJ;Ud~Oa;#lq&1zPKPY>~R<(vU_q_be;P3Q6i3tSpk% z9wjMryTu~fxp8!nTI33(XT%*1Kld6HD%)%bb8>9QRF;etbv*uWfQ=4h?VhoW3f6YD zZj66ftXa4I&(eD4-{wHaI}tB450a8p?o1G zDf76^ceQFn8W323$ROj>C0TRYrxPI%8U32zP&fGoHmBg=Po^%xz3VVvcZvOf_TtoQy5z6ZX*%jG}R~>iOS$d#`K1* zTW*+7@;~e8g(s8x=Bs&P7z~ZVUaNP;)Xmk|0cA;gm7w$i{z|m*zS4*DLKMs3rRW86 zBg%$S?qbtZAB6=4E zCPcXN?VNBjtWcOUbYNwF>@X`d2Ah~(! zGR2=N!mpr^&+hoKm(XgGue#+h5Uf_8-HIRXs{|O}sB=ev(%JRZ9aBmjtW}C z6g~saSe8coUpvIHN{}O(96S5WUz0kLRTgb@+arnM`WSLNa5}gO!oiu)it-{& zVlbq=f!gN&)s&%{{1@-OCh5fDP&*orPL$5Wl}>BtjR4QZ{Y~w!D~zi9AoN2``g-_f zOM6%TTzlMak{bsAUAu)L1aj4xV)Mv~UZ&-eJqDh)70H{7MZfzMTo?Ot~~m z2HorzbJ$*$TRyTSCi)4lDKwHLJIXdaJ$eef?YJ!X$AIFtMUUg77HVfwpfc4?A7h|m z1Ef7Y+k!a+;_Qj@5CBb@UEbVCvFvp^U(kj3k=t?03kP(1WF#n93lM|a&)Q&?h+g&W zwXfgact}N|SlYF8AS53T0_xT{cA;D6VwTpDqh4+O^JwCBRP_%A!hc4K+^8`-WJcmk zebe-{j*Y+d>@V1UN!AYY0`fBrlWo$J@!HvSe!er^nU#hjv_lO+vGC*}5PA}3t*5KB zi#D;2v#0K7HPtqJ?3G#}jlzG+VW#V*xF+-6Vx7oKS%m&wrrr{Gy`BvQ|7ZL=YFWmf zzFEJZo%nNLW7wNwr@U3&`aO?m(z))SWzSAz#<8WM#n;yYp)-ObYWPg%uO`Xzx4n%C zn!c%|kk^oF>kIZ9UrfrT_MQvqohbmjQWE6VZSrE^r&rIv;wu{<8mSJ33zN%LuH(be zi1cSu5e0ORMj{pwKF2T9tw}pVbMMBUgjk&Ezpy*>Fvc3eso@R(m+SF{OIKeHfD4X( zH1Sh#p6>6ndtFxNt5rbsSz+LbAsr81k+oR{BeAJaBd1N0r>$B2ilo{!`cyjC2df7tpnKb(!`X;;&tBdv{AF%C7KROsuSR79QWG zx(S%{AQ250|UR{js|*ViS&en`iuT@n=(A-tV=eD*yK_JAyA*xA`42 z56K~7=$^=KfD(Y3mV1=ip=oa^XPSFtNW&=Ced>>)YAhROu6~vbkRNM*3+Bzt zP2CIynjeX0skBYhuOEwlWq@{I5TuR9cxDc@T9vFw0f}rkkB%rR5jE^E$YC!2f+(8c zcXZ5P+!Ij-IdV5epD}GsrYYZl+%Q!N?{daoCIbp>8a95;CIZxJ4{0sF-8dtK3XDsX zD?R|(j@G{^8ja6C;F9a86P+nk#zx($f-}+QPJUzFfkeXsmztIZW#t&B7|{T({DAp{ z$e#;#*cRb`v%O<0=UyQ#)QA>f#9hTNS4;E z6z{3OyTK2~`&EQ4RoS0ytrOt4e#hy>#JA}`4ljSuOmUL#gz^*iQSFNw1>Rjxwsd{` zbeKKu(%jLq<{kVPd4`gfpoj(hs2Y@T{8n^WJw!gRubIjFglboBEe_v?L2m-rSwWqj z#?!v_OG#~waO5_}lTm}}tV6Lroo z6Qj+EskJvJn(_(u_;VdrF=L68r-Hpc3f1|!PU~?Mq$AB!mT|KVZ8>GROIai=6vl3r z@)FFu?t}aO_Vq&k<;EZ-BMwN8)6-X9O0AP_ng-LCh*rC@&`Wzxt4Ln-T>52t&Jq_F)0Ux;Fy9=ms(2418 zvBre|@tz-ZI9^j+kgzOBAM;;cB=FA1DiQ!{+l;9CY3EBlGm|lac>TH9Q41*q?ne9+%9ZZ!5 zZPlSUrBf>mBmQDB+fcr}Xj92l7}lIcMDoRVaP|H$Q{f9bZgIk3z-bwLZ(0OY&~mx_ z;C8IdEirYjl5Dlum6Ex*jsj*+`X3ursrQ(~HS!cvIGO)&fq*N-*;~%2wmcdZHX4+j zSpq~L`H1Mi%5(qYj9qwpyD1D_HM}3Fq*pUhK?X_R(fcR7Hrtf7;AiZhHk!fD+eZa8 zXPSgdVfWa*HH__CCkCsuM}i4(HAHE%KHg}6v^%7w>pplXudYx1j?_9ZA$j33nGe%kblGRRUn6>rpCF~9YU(Butc6brs$`RT6_kxx*rE&? z@ThZ~8sJsd0HWOxK?(zw2?cHZSfvRJeG)y6P5+rCc>NFKMt2U7-X`qn$ojqI0m^~L zeXDEHtg%rs?_n*vEH5Q5m6hsUnNp=b!Mt3G|1&3qKwcOGZvo1Yk|zj~3~X0yrN8%8 zMPm0Lf4XMjEOoqXbNe*S4@+qa>s*uU=0ryx)+Axao>j#QshPB(a47Px+kE_!g?s<8 zPD@!CQrYfv%e%DTf$E@h%5Z2O29i#{#<*PboV4| zA#+q%FQrFZ`Grv8;D^?^Z4O4S81^UsGC@v@rq#xl-l0%!MA_f*)T(3ZsL%qo6ail1 z_4BvP8jzaK!mjrUi=tzr>NR_#Lx;36k(XLrQF~JOfi754xG>eY87X_Prb~OOn0u#n zbGtqD(GPRRA8xeB59+gOQzo+wRL4VXpJS8cO@bE=4<(nHdT|tS)Tp>lPP9-asnPWb zqjufz612Jomm*u%{tBj)kxjH2$bE?xAhpePHEmkvLOB0$Yo_AfsVvh^gT9h9@kVjHQ z3X%VfFGg_mE0KKDT5jTwBTK#XL$~_*weq8Z-prBe6HilW7p9u%C&*#b3TxaMRj&J@ zw4K*Sl4T1Bejx%DAevFTTacGwTR`^X=%uE2w`kTM`nE&6F@0!Tien zRm=P`=7~7DAte0A6?kebubc$#FdufV62u^2BgXYxs_SLIn;e3P4*^x)J-|10;g}xY}P>(KBsH*VS@~i>#(+um3vO{i@?ltZ zdZ4@=0Ez!=y=Mxqea9n_^*&-r)vZ-`c&;*1?{f4uqm(NPbPC5M1OZ#mAO^yAhlKX< za9sfisVx~rQfT>jTo# zoEol;g-uuW5?#bJBPy=G@jZ~=7opIXLj0i`I7o+j z?2_fp4CQnqx7Dlar_tzIt68n`e_GU=Fd*uqh1ob#;q5)qf38th2b+yK5hosC;mPQT zo&XtAIjjBroNNi!!75;*evaF`_6xRovhmy$lan5Ji2p0&+sb=}Y^gW1zZ7$z?{o0r zdc!JMQ^MM@jYY&5Hp363poOpzJZ#)|SQ%p!HGS)Q3XEeoEWorTa33L|ubddQa| zdR|OB;m$8y4=7qPiw-NgQxHX-sYuiBeSCEB5d2qp;C!A1&`gmoh-bp20=~Q=m59_< zwfQTh8cxmS?e9tk2=&fmDOENmu+~NcpmqY7vDM#+h}0g5OiL6&RV~8%OJFnPt7?}; zzR$}B8HEOK{pLDY=~}2vo?N>ZneV{>M#*6zsv9V z#l5^sgl`;aqpD&^UT*@doD{ZWGbo%LGE1W)R?|aaOt-|YDReaea1Tt2-CC9I=n2(? zB^n?0Bg?EXsVcrOH*W@>j90twI8mh=-%FQJ5}+{2?qyL?*CbEwXs*Z^O2z^*Qj4XM z(s~Trk`_?`^Jqjp&AKIhD#GRHFs%NbYQY2`7ii}49sj)&;BL<_FB;d+y}vBK;BR~&26WsDpkZra1}{rO ze0f^^!F2G^sO0-{OVs^~rP|gTU5;*`&{slzS@=NT@9ju8?{U=mVBeNHn>P*n#VsF@ zZt`F>429p4#o+dHb^YwbNai(nE9EZb;#QeBU80Zj$xUx!z!E)LXhwzqopD$>ulj4# zi!b<7ktol^MCf-bXPeW_U9sm51S7(625&yc8V`Ud7P_6bB~PkQWWdk{=jk8ynA>H) z5@e2w{`*TKRIpa)_3_i>}nwOG=^Jr8TWd^c!fl73XUhJ zBZWH8535hn(Q8?6i7ZDXY2n98;YxWv3?G>>v=Or2!X3Rq>o5D+#^&;0$V&_dNX2~M zziDSTcQ;^HOP?S8v73q)Xp56}Z=WhUo?pIgK9VfYk+*?lO(8NV45nfh=biA2Q~>u_ z2@quw;#7zyqE}w`gIIabI5yAqP=SX)yuC(M;q66M08Pwren{0ZrCV&@Lu+CEDYw3P zWiyql_@5&{F%HiDg7|2e?OOt-F-hPTsaY`h5pG3_BVwMX%RHS=&G&7Wu}o2BpZi6m}Pw0~#`xSEq<29>g{w6f+?8l-XoF z=D4MQtJD4yXL}YKW?C;HsVYVvMAOd;Tlu}(6Ba{WCY!WN+J`wJTtdgDF7^p3T>5%v zFNSTF8MmXzGItpx#i0mwBz@BO_T}3O=VAGOT%0Vh1hQd+f$IMFIblA#NFq5QJpK9_ z#TSQ8IkfMy7>*)T!Lyq+#A#8!@jjSB`fRur12Ugx{^ebQ!BlcIdv5IFdwJQXxSBwwBey4AyyqW_$;e}RUyBxva)R^`GLJv zusF#sd~lY~fhxxnau&NGH9= zIpTR()0hk@2#9BwwY4X-5W449; z8F38wX*`#)U2BUti#?yKE19=AJaz8J?%Oe>5a2nOcZ*IJqEKSdjuTaO{^!y}Ga;yc{W%!Lf< z#vWXAH&{;J*osLv8b0UcDH+q*6msy>w{qY%37rCIFa7>4ztkm zJ+_vxsv`?g#&s^->OUlp7y~Aow%1g8r#;5cBllZs?>_%vKcqP}*29RZ!2N z@yVEa;E+hgQeUf_EHxn3W>L~9B%$=_EDON(7D`@#kldKGma(z#0~zFe7_kh-!N~@4 zA?^l4Hm8?{IJHxihEyy}foxc?kv!?LU!RCT-X<%fWDWFtVyD1&@pcR^8_cn-3;L)N z#m|>~m8&BBUS0;;U37Fj5Pg(55evRGT3HJkd3-=4=^hWz3te~IO%K_(|9cHA9|x+F zy$Ts8y49%C1XJl$%xq(Tyfg_E)hvVep}CDz#k!!)4D$nV$@Ra-vRCM@P&-Sc+yq=F z_)cew1VHk=#pl^Gaq>50%QRt_e2s!HFi}vOVDT9d`Y&<#+P5sNuNy?J0u(M2h!R%1 z0tO%U=^NcRl%K^3M#WPJHx8x0UQm{{WKwC}#&onD0**``(M;71rzEHLO@lOr7K}Y# z6~F|a=9ihc2`?DS+nQ=j#31dpQLLO-Ds%q?NAzsuyvM&`XUGW$6kt0fZ z8d1g%QLEqtSeTjBE+5*BZPB@wuVWw2|1L3TV733A>tMcjBO=2(un*jB&QYrnGsRUa z4=Z4dB%k$RE>x<-a+WC;x4#z5@|7F_c! zW;MlJ#HF-gx9s|rKIIs)U*))#nsT>7g5Q5ycy2)6M&|X8gB4p1${s#~vRGTDbNr$m zT|kbQ7cR$WY@tB9mau)!%=(7rF|qGIuV5J?oiNdq#R2-Z6}Go*-JE7t2l)@b4E`%U zBqZ!yHVqz%+XBltSPziJ#3dF>zw^VMCH7IFevAA}>S(PxC+OYsvEYi`i2x^bG?{BF zsk^CUVT0FV+GPAhMXy7(aw4YaJGe&QiGG+3{rwH!53h|J@5d^G0$0C%>Y2F~Ot$)h z%3HXK?XR)1w?%{z@-KdSvB6`9;J1F-20H^lQBd6iKulQ?fjWe4`W48Hm} z!D~Tk^J_5}wJiJj<0GZ@M-z`gjK#&y6K9`t{9!!3=i7Ak*;r161@d2Pa?(K{24`*n zxx&Z~!}>Lg)0=Z=&WPshhjb*t$K{P(>P4TqSM`s14aV&EN+Wsd$ncGJlW*9{2k`z6iB~oPPt1UgpM4q)tXk4 zUu$qX(qLFbXi#pk>gu;q=ZJ739pMK+$jTEkaC)f;op|HhVejwo;T9D>AZsj2$_6$t z2{+>!ky|!I7#3{B=&b-P??)E*ZWB-^Lnl70&nBhNr@Yz8=j=N~r|I-%-k6U1GtU9> zOhaoI#pR(ZMPDCcA454>4XlMP-cEXE9W6UPnK3~&H2nlKm!_H`TY~v1Ohsc-syhMu zHgmgEGSr_2IdWYJeMfd{XVCYJoME69(WS<)N3b87Toi?hNxx^7`f6eVoNHX?F`NQv z$9M$harX8fROiw3?w^G7P-BiVF>NZ$z7V{L!*GeBm6)N6DW;DHyeKEQ%@?Q0OS`OI zHJdipG8Gzs&lZt8Q=lhIQUbaleV1ht>=DBm@!Kz*_~C@_F=22}a_%PfcZIk(g8A5w zBi{q^c~|;+Lq!m*A1aJ5&Si(Fp9|7(4PovX6+GrZVf@C?CN%gHQbsU-jinBa6?wbt zMWIm6jdGgOG;5wNN#gh~u$1W4${4TukmE9^%s z#NaS;=1uM`{Ah|0{(K(|h0NrA9&%<bd;ekc##mY5Y+YCH47^c&dPE-Bg#1+zufw ze>$UAs#{M&lp|Dd_opw6>Z$#3RB3mOi2$D-W!X zt@;56b)RvphWVA16{6ml*v`8`<@LY*jmf}x>^b4}@#AoiA4zP+00 zfYLB2BO8;x-^OGL7D3Tn@e}^1B#|X4dHV59c#A&%b^=xGw4dsE7N`b_rPJ}sQ(%k> zKzD}|MEoO<-*u4RM@bf~Mc?1#7PZ#h+78eru5lyoq{N9paV9fC4hzBJ|=4tpu^G?`$X`B+KGY;a$h;JtIr zIezb?<E14Q<2<^9=IH`h$X>ikzMd zR_{xUqSNIjXmt4m4AqCm3`Li2ezf`~gUVm5#+~+-Vo2YicI9^L{rMBv^)T*!_UZUB zsrRH$YU1ji6m_Lk8Bt_%^3%b+luG@}f9Je*8@gzQ^nPfc z9`hsx(Id{zjeD%VA5(OQP6-ep>@x|G?(Z3nj)sA=jgz*S-AF+YEiWIh+CZ)B#Fk6R zYOGX%r(z|&fRetD6*8~Lg{GhE>sj!u(xTcE zCbm!6r2zx28OegO_Y;W)8hUtW#D=sAG^R`^^q&v*Y-sQ0dMK}W`F13uRHMT3yS;jz zX;q*3hN2CZ?Uv+Lfq$=lAyipKBQa= z?8<3LC3#&`{DHckMyoP2kASSJK%9@05aY{GNmAjP(ujz>hVA}&4W#qb0Dr)wFay(I zp@v}^JRar;x+o!u_O|!F4m-CW4f6Z7eXc>{K&|tkZNZxcy}8b^ChPUsGC0YuklymK zZNuOH7xv@TanO!K_vFD%-LI>sRlP@W1apx+P5TsYJ7JO)uO-O_P&VHQ>B@#tOOaXB zKRDDPnbf1XXau4k{;62ARR(PnS!kR?dKxl--w2y8dP#hJyJO15#^zK?b%2A)D%CHV zg?qd=4m`2m=j1>(Iv#o$lIG(mXS;J1zRkElNHr52M0v*OQ;LOKAc^s6Kqy#YUx*L^ z%375ZLlJRiFNg(-F*h@q@Q;$RwWI4Gn)?n%2-uy?5xuMe3fQZOBD8`m1g|sHHGsFElJ%n z`})>b{(z;WT-51lGJ?g=?`51p-NvaCdR#$JReAN(8||5F#Pqv7<}eY^{=nI5<4vyT zfO@Owww&sT99H&vw@^+V1*RU1K^zY$3%tPG-KD(ySDft`{Maq5ll-boWI@ENv&9~% z!f@ms9WX$qtgv(6NHzd{o8{2vmZOhjk>l>`i8Y_ezcBMI5{#CQ?VLor3m+V2-ap70 zq(DSuC}_$(X~vQZev`Fy=FfpjiOpkM@M0C_adK_Q`^?bdvHkuhc-!xNnwfNF^=_s&o@1F z{@JM~ia72@17}}4Pl)l=RBi8{ptEm(I090W8XxG|rP(J44;Ns!($^k|P0x!>W6eW7 zO8|OB^pUJ(@yQwY+)FrY%^z2rgk3epS9BjQ8^<}*G#K{LF&V0R&(jn_ zHX^7#xf!`w1U+jaPly#LKoKrRMeuyyZ>H=y&B(6NfVju%t)U_c-w+6j>fza$wpoul zdB+Q;Qc0CQL%98&#bM*yoO>$qW+q%O!g%Xge(_w|Vr?(SBAD6aWol4s}}M1R<)T$K208H>lIBHWhMF=j-Dj`0Wmh0oVs2%7Vn&9llfI>;ET3B$a+RCVI{bE)&sJa0#h zCxQy|41LQnO%B92mz`>)2YiT3`Bl+a>tp`d8Go2bF*OHn`SvYkLKil6)?l2>HP8Sn zCheUP^8Ly>P}I^Ps`q<48KA#|MP&%+`DaYU2yq7Ejm@e_iZ_hBD31ifG%EZR_9Lmz z1jFD{JhPk?0*Im{gYU)Bgfpc>8oOHatebPfF_NXOsc&^_t=imPz0oCKsmX1S{^_{M zs-S?PL5%1J_fTmig!bO!kd{1wk#b4$W32pk#J&xjGl1!@v@`hL8D3EcbT@!kbx(K6unu&SZ- zinqU|1jbc;h?L%&_oep`R~}!&5SVk!U5~5{x&e~-!*Sxq04+`o0^{Z$eQNg-WJs(w z1|z%Xx-G5Z2sJsoK4FroQ_K}Wjd)tZbF5^0(kaSI`bR>AWS-NMidqM!Do9(uvIJsV zf$FV(z{}+z^vm8L`-c`3uEzg}PSgJh3FkNU%H5Y}+D(JiTmCt=vGz42jW>T{X`s{q zR1gmXsLAo;TF=7 z2A|3XCeHqs*G}YXs%dghZZS9& zncq=Ewe4H@jKXf2!aT*LfGxL#id(DZYA5 z(=CejkjT7ls0}wO72%^vmXlc>2Y@skJ zzqe-5k=9Q1v0k}Vj`d{Ka4e7Ra_bZjO+7@%GL+jXl~(32QIyeHod!&%51`p&9C6Vk z)?-B4`m%Xnw7F}^8eFG+O7zulRe!~lp;rqcuWO`eL#47ye&%x|7YiNdDpe2%!MeGx z92!;3!++9L3e#vQuds4sUnoo_KQ4$On$i@cd0H;^PTFz*Jh2{_psr*pR;GpsaZc)z z6s_h&@nC$K(SEW!{F$6;G4F=wS2X!QaWG0#Y7IQAS^Ctcel<0TYY>PsvVU=k+V~AmFcqs4V7};r%t6%o+#eVD6X0auMPi$1`87P{ zd9DHqKB~lHV=PMzifG`2PsM=-a$sEfJhCd6@%_;eqwz~j*Y%kDH_d@pH!KZ?ftT&UqpSB>i842i>Yn60 zVZ1hxmwS4r07DjuI0!mPp307Z`SzIlMdvN2KcwHj8#jh@` zl%54<1+?(P?_menh*|_7$Mut^!acXinnnXfi2+hM@Q{6o!GNutU{%-T>Z*}j6!iB@ zNs3pu9Lt_953D$xMN-JjZyx290D%#$gYyQ6og%A>X%x9tNxEl%E6MynjCH;@bfRD1ip{K1PjnEp1{P5K^>uMOdn{_BlDqf!;<+;A% z{PRk>HYn7{p(IEP7hWU~$6j8n&(TCmkO55(l#8G?#XRv(G$h%KB-XqMdQ>tbXrhiT z<;T_;gD1yDe=OgkpgxFJRuMyas^XEpuN~h(Vl=**OY_;@9i0&zm|yt*G^SkX=tzcb z0-J$B8UzSTU$6f_;ghhZQua4g;cA8FV*BPPIo%W4o9VcV7ew}taOFLS*KmJg|;di7jV)N8hFa?N#7g}NmT z=BmRn_QZrwlgp>8VgTrdX>4fK@sK)+I;|~M_vbUOgZqy3q`BG2#{e1b>pnTeC* z7pLBU%Ctpt{)ORqN6%kdSexfafu~7t-X9y+6fvnNn}f}56Og_-j%7Z!E3rn4d_OA| z<{(hW=*Hi^hn8NNakt={t!SaxvwEJcn}e?JZe6?BvR9G=CrZ4b_tvb>br!|uIGgnH zxubeRn+`pya<@k4{vSAL5C?{mM-r}I2>&ff1{d#&I4e6PA3!Q6dhpgm}%p*&wNYuhj{ zSnkh#A~WJvGq<|cja1@@mW!^Y+o0Jo856-Z*}r$O*vqIbm7kvre}5tKo#Mx{CzXwl zEDT*`ji0fEcI1)>zIPZ-wd&TWEl0ykzL|h$Sz4dHzi{$09U4EAmU)?I{jDjkou#qE z|C^-zvbmO{ZI&%^#!1}qG~z)H2v=^H=z+p;W!>8fGc%o!0WL0+17$V2O zXPz)@jnimeJ@6wJo#CzDDHYXdU{k!WUsy|ZkIQR5XJ#wuLQL7Aly0{&gUBI@YB5Bc z1Dj*4T+{STL`L;>6tm6iS-BF`YE{I;dGPJw*^KUaYs`6hu z!7s``c!A8NiJ5rNTVEi7txZm+dQ(L3Y7hUI!`u9u{Y_Sxj>^MVe6KRDm$Hdcw}@h( z4o%&LS|qGdH+pH08Vu)Jv-=0+)9LyuIfQ~V$?JN5m0M>is(%Oy>ihe=9Uz|HcKqz? zh}WJSE+LMCZfUgsqCB(9I$nFlZDTOlD<1Z)~H zMudb4EkzU>KlOs_+cjG%&Nt$Q>Xd-U@0W-N;%8(g9}e!j1|EUP$j+Q* z9osKP%$4btaoaC;mpMWy$;f=xGu<_ZI7OAJsCMk+@~m1GJ-ly27H|6<+6wm>PFws* zBh+V46}~hDxNGnU#l81!=Is8M#%c0(M@mlRFWK{Q!G9l~ujoj&(jKDeE`H~muZ)b4 z?2M!h|HZH+r^!7-S5gtHahoT1kJ&X0RhkO^Ge-Muii{3VW9wpNE0R()JFBDKU_+}= z?dFGnPG)~N)dLP8AUu?|srvI665YM%(s5vd15)HOZ?&KsmH5eyl|HJSw?qJ^r1i`> zml=CZzoeUVtf8aip3g#Si%rd3^^%J_?AVYpoEJG&&o13?j!BSnM>)lg_g8gqD^$_U z!x)NGJvpyrtH$dez@EtQij;v-a!10Vehz6pn7lhba$eI|mk;5h(E_{nRHE{8mK47Z)v92>P_91vr>|pe!SH-lI+x}E0JsUqT?r6ChKJ>MtAWXjs@NR@jR zDMwv66~^O{j2D?qXf4=hPX(Q-Qpn$r`nvQI;Hzj8*;>?)I>y38KA-dcd=t&Am|@Wm znI-LGVs3J4lWG*l#f6{id(CA&>gGU2-1gfO*K41J-Jq%&ey8t(8kFMV;tPv6<~$#R z(v*3Z&wIaGI(JEr*@M~R{j!z}jtY^u-@ycBbu_*1u-jS%$FXCCC&^Rs$xO-_2RRiY zBWU!en>_*yFiF7Y<8o6b)bPznnXFM(Hi36H7-tnD7ftW`oXOCB=~ve=KV`PgzMeFl zSI$_O4%zYud}-gOEiLN3N~7x(dY_ zBH62ek+sDanlGyt&5mkW++m&{K8%dC`26*bx5pTLtvBf)RzL2xlm>2bW_Cnl-+3*5 z6Ysb%zV5p_XUXZ5&{Sj1*-qZ9^tY({1Jd*KdpENA7x(2*?AdjXq(Mr`!t!!8kY8X{ zZa+%?nkb}xJdDTkp?`Px$==?p2M-b6f$jsgbsM`GZzLI6=)>b`PUvUEGUF(FYw5Pe zd)*~e@w&$-HHuUP6Vv)ZYi)f^N~C;X@jdQYkDSMwX;7SuUC9keNj)`GSly9X0SC@Z zt5;Q47IRZa+w)%8kubp_OP{L6qx18AEcU^con5!Y@#!Ww=b$F?FulI#=Oaf%f9not~89!S+bRGWcBn`Oe@3eoF{>Va#>etl3^!|cBZCaoN~_f0x0(h|MHE9(E} zxCf&Tx$ig)y+)l~$O}>H6L-1j7n@td{N4Db{qAO&g%be{Uu*FQk$(?dzA`Q2#xvS?5LS%x?OAk+u`+eNZ#Pt zmuKHReEfe|)mTmw!L9^H4B6i&ocuw+2fc2--TMBz{1aWjqez(~G-|l#l6~GQ-rnPL zdwTb_M$)>?>@YdLV-SnYN@rtLZ?ye&2=9}4zO=A#_@^trQHVr-*2;OP<3hY+#Qb){ z{K)6-;4I+Ecp1GT<+OIb5`UZci(SJm%&J863fm8~WfLR0U2=c13({on`1uA{_}AOC zELOi_q+xYO|KMuyk`XabL0*=pW?PZe{N8A-nMTr&B zE&$)JTeX{@cGl-Xge0{-e(OoY;G@ado49+yb6XU2_DVQBDuv~SVfkj_`CFo0706PX3gkz}#o>D0KR`Z5}dD{!qtz01CNJ$Nav;nC9G{KG?nIn~<-iA;FA zgsWl`>X)RG_s|_-zoYd&H{j1B>_4wA+yT7K^%B-`Ian8b1~Q(F&-b!8)+eO?2>?U+@u!rVM=p1ZXk1UQ+>i$W4ZNKq8h2ws zzp^prz2rm7cQ?z&n)G?!k0m|Yh-#>o)WcUguJ?Fwx5OK7a83Jg9D=oZO)eOPFP!YRN;A69l<5!i672o_QH zN`y~G@Ahzd^3iP>x+-V{&5&h&1v=DH?S=x+REG5eNqNTkUdh~db3eohvr6Yw?xbuA zytwb<10>Zr&{9Ri8#WeM502M2W)@Uh&fFWiB>LctegO;CqxHn{LpL|YggA0Fc_`bwmmWtUu z)8=TJoB~-4>hXg*B_s{+`+0gul%cQ|Ldz_7@-z;za&0wq>Lec-H8)o9m@YidT^gL< z*10Y5*~+42&I0l5Tw#_=Y$ulL*+}}yW=M9xY1}8W$CMmVEpI?ceFTM~E-laYvpJ%s z#0t9DyKH~0lZYzR?zm}Ew)OR?cSuHL7GD9MX^zqx)94C^0y;H8iks>PaESI|quxIt z_p0^uV1z$mAaw{b;bpfOA6A^4e>M*_UW@ay zTIvTuO)#lV(Gw zx$3If06;hnZQk-Z^;=ay4?x!dBAyis;}3K?kVnMe1HsP`>dA7 zz!PFg=m6r#?i;0`ankB_^KD-9S~xs7I1v7E5+pb?_Tl?~Sd)YQsz3ww{g|E5fVw2HHn}8CO@qdc{Ny|9<{PxPixON9B@7 z7$9jTC9KsQ3k}5!hKd*#Kf*Aj7&vMcC3^j6dt?1Bmh~C@#u2kTmU6b1MQ3A@KxDXM zc;fgI;Dad6V({0x`@X(<7TUkIC}R$El;-U-ibS7`ugG_^#M5Cw+ugoLe@|bSwY}$L zAjgz&VkP=)>nx=i@t3e7agW(w4lgV$d9=5!{<++de%Y=F`W=B{?#sw9&g07@MG3q- zJi1IOqor-V{r#YRhq|RmIXro7|8^xpRxw|I&Oxs{B6#sub~Fr$Q>G`6Jw2Bk_kJMX z!J0fQ@n&jWThz_FzL#!jrP4YJXQu!6ea;NWqoY9YGT}{u&+>-d?u4)W9X`zLh|qz_ zPrl=!1w1@GyBMWE2#?o+Y5C0_LsxH4X-tWFsWkhPqqca%{7AKI)@kyd7k%^u@0>^; z193VFCuM75(0#%X})Q6)o(8e4<4GmmZ81vTF*}RgG6PQtz-%dxq+gQFvXLPnE7BEuAwXO(7DRUP#pRNO`6e z|8o)dB*l-H{&AxI%=>0KSOgmYnWXU;SzR&${{%tEbC7Zved|g#W`nYN`7d82kG8e- z`1lHe5)7V}=hdOw)fuH;CJ23siiq{LOn%csj#5}p5f#L!SWz&O*>E$ll?52C18L&@H0}8rj`AI;s|&x-MCq-q`vRvjX+2 zYl;Dqq#E;ezd6ZK^p2PXrzC_nViJc(D63*Q%Gm(qfzP^$58eJZWK^UPd-&+R-Pp@EJ{Rx*<#_9J(DTgc4ZDbttep42%~` zr^1o6os`bk>%mDMgyQBjWHM(a^77@eXT5X6YhJTJz#Tdfo%)EiwLf9IuOdcl{cTDmi)qOHnW;h)_p z5q}>_h=diqK>YZoqwZVh?W>^8vy@^Ypihw}ng<8Gm72uC=GQ=f(d@!rP{B+}{VzjQ z*zzT2`7)RK$^0(r)%mAazy7;y3*w4DvoV{2r3b3fy(v!T5HgF3jDD|QpteKyvDCf3 z3wHN*gjZA>l7oz##HkbPT`^If7pWZyw@i(Mv-a|K%kLYaRE)Ie>^UL6xPl7f48|m? zUoUuctzi9{SN#cnODPg*+p6C)nN>=?lB~Qy z&>5eIHIlhUlH7eWGfm#AW^TfZ$uga;h>}xj(+|BZw$QdTX(S&d-2Bk%q;?l&$p8|a z@MeBtU%~fPArmDOuA(a3<<@zH${pq%0*hw{rC<+fmg&m%xHkH=L&Hqhs@^{+N<>l` zq>N3N!#{Q&{_AfYUU(&aaDFbUnRpWz@uEg76IJZ-CCc(O{NjZmq=Au<+kqIvYmFKJqim%q9TfOIhIaq>Z25*1#4=u5J-_Gfjh-32S$xudxndA0 zXn|Pkx!ONY6@k|%H-Nc^hd~smFt2pMCF_~%giaPG93=F*1AI!C0$0pa1%*en-Cbm*1f>r>n4i})@|n$HcF zf<)x;uByOHd3xyr<`X@hRVX^>B}-@^8$){6V+Qp<3&V^jy2?ND6@I6S7sXJ$((6^* za(eDcHRA};O}byZIx?$l(joVux`o?!QsBeN-G>xVURHadi4Z6M&MkiI&b<+#%&bnv z`Fd)GqtXG1=c?IEMn_4}UCPbyjgqUC3{*WlsQBtC{^)X&h7o994;pT0Kf$mJTHgR9 zd+DI_h@k#ifQZX7xb|UgR+>>|=;K8702QaAGI%n$ze!s11w_*G8EpG)Bg%@h2pc+y zyG6|Umg~>AZR!XI zrK!h9%kRHU*L{cx4R_!j@s4nj3TRk4x*$@=3WAAjvp}GU29jw*J~`Q|EGithlt**W zkZn+uPpnrolC_|pN=Xh@i5^c4r{d9Jggd>-bG!5HbN7{n57q3Dx!30|*CMd}2};0G zzH;8h{>hkK_J`iUhONVMeb}5{$YHIRVVNW|iveCR`jFI`2S%UiST&mDVUfBlEaip@r|SI@;z6m*ou>DP6h)$IYcj{%!I?PR zwCHp#9;-#5hji$Sqjk^MVphCcgmQ@wnR#dya@1T82r6cKg+CLZzh2#?bga&u5Y^9< z^ag&b|1X9PmnKKqP;U+!FzWb2wU9(-|6>|Gj zr?eV*@~gOaaLU{`(^;T2x2aw|q5&u)DCa0fyNALgnaQS-2_2A(vXaTvH>}MJ6B@qU zBsV>t_EMWrgZICqM3{1(IS_K?vpx^@<_z-z!^HLJP{RdSDxL;AsBh7d+(q z>a!yR)|_{WSKy!G79tqoVawj~>Np4ET+b}+6U)3u7tN*kQ@mf9pae?BuRqwynQKl7 z)ZXimrGYc zv92d-O}FtucO8e9(^~<%j<3{OmLlit{Z`#GjT7v+ZTONYXrfCBrrWd~yQK)Ojz;2O zRBMfuyg*pY!c=(>H(&IaxAC7Rhu>(jWvZ;66|uzr@A+`QELBw(2FXF&h0E~W+obmS zN?xLPviK>6Vo3~@E_oyUPE&g;%}$n8Y)3^>_G=~9xo3|C_V6up_Zy0*-wE^I|6DL$ z=`wUo%aY)+zH?wetst-oT>N#@=k+&LeU*7Jl6?6;a5)a+OD!_(V1tVnfR<5Oyl zo%f^>!7IMV*-025Kfe~wqn}(qoZfF<^sjX>NgBXrJ*wv2{i-;_RR(dlq@8DvPj`*c zoX4Bb$ZITqw^VF60{$aR)c(zlPg4_M!HS=BHe`C4Gn+}Fflt2m{i)$O)W$Q`ojfj@ zrn2!2JUcrk@g{uP?rE=J=5=SNmy(i46a#)V{A9Kf;VHx+lSTt8Gm5(Rc@v0JF_K8h$)MOl>kVGn z0n3yWp-!(=Erp{8`7Z+y^#;kvEk!M7&o^m}kV*rFecsPW*10osCYwbSGY<-uwuQ?X}j}!%k*c^!F62~RWUu>sdpT>JHhz{Il6;`ueB629m{(`eZZ(f zP)rD-0E+ssiI1NlN({?Df?wZ1VducY1j=MCd>O`PEKS%+(>B0$`|c1-uv?b8rHL() zul9*0WLV9DPT8`VSXl7cW&7IU3b}RCf!JTJ`km-T9LV<{$!?C_dyHGM%k>gl`cJ$H z53{z$%su=)`vjN)mh#$mBHZDA*YOtrkKGD;ldiK@HxCPWK5Wf-k+}PQvl4tLhS~HD z_=rr^c^XR@`|e>z8Z*m=?1C$Bxw@9A;@K@0*w(BJ)v*6&EFXpITsR(U^cp+)GARD< z6GrL`HqlHjziNs2xQH6m|b^3%6Y}qm+Mr&+%S+2&La5Q)9ba?x|0oRP2 zHXDP?3pEjj+1e>6K6R=4dJ=)4wLg1G9P%y4r3g()fgV|K6&0f3P^!c1aW|nc&pKU! ztakY*r(ZeDKn*j~{kT9L+8w7!;5aM#b=AxLLrzThJnc%B?mO?*3QI>v!NZ=jCO%G0 z$*S8y*X(Q!>_58ws38SnEQ|_{aC}Q8{dFxDHrs8};32jy>=r2V@i|HvM2(rEJZU^6 zITmq$_wJ-?ej%uPHU+R^13llu_u3C*T@PM#9KX08{CYk3ic8?OW9H3I{0+R>*^X1e zQNvCtpQ+x*I&uav=3#F_lOX3Tp{goJEk?cM{L#CT@B-oq1fsyAEYRi2#0b zZJQj@D~0z~4&NSJ5e(%#YM$a1zV@Nuu7euRY7q*R8K96f9$9XhE+qPX>CVc_Kw}qO zMDSHa1DpCgrX|tZQOZq9eLYC#tUPr&w(NbY1;VOfUgf=Gy5Nk!D5Zu0z`vVo4fuYt z!5Pu_0`0dlQWP}Sek_pPc=OlOOu9uOISqUGl<+-a?_dYkfh7|qi}f9ke)H-=!2+gV zUf)wI9dt;p^rZg7(Cn+rgk5jI-z2+c?49yrA%At{5Qwl2jfh5IW#aoNEfY!mb~65} zI(d{o4O#hn{tvs4NnmFmFR|@1v4g8^eH3FmmG(Jx!1%hRb+IYt=>zOF9!MPYDe56d z>(}8Sl@h}J!sC0L&jgfmk(Dw5^A;vUnH2g%S8ti)ezeo3sG@#Jgmto!BCjHpn{RP; z7~NkcICe^PZA(}naOtU3&B#=Ih6%kpJb{STuMCoiuKwzWXn3zUR93v*x}hTnpqXih z+E~%OT}bl$eigS&M!F5sbmdE&LpIk z`(j|kA$Uk+CA%$Rj&~jYj7PC!KCw60Mj}BC(7zQ%=y?W;2J=p7Lw9M4Q{ilji{HG+ zsA|vXW|NXxEh{yKdeB>Yo1^*9SxtB|iVv+9I#2|`>^o_%z^mQQ>5$LAqDhKj30 z($xy8hNEhoPfg=(q*ed8nUVP}5xB|!Erl3EfUOCr6Id+`E>-m6o~r&SUs#st(OWDG z`vAVS?)G|tI1aTz^F2qxCzKkW7Aol0ZP`7y9`ktlYkAu&Gnw(uBH$>jVk7hJldoH- z+WHrSP6}jH<#STe>=4g9;bu{m(m z+h?HTV$Sv2V0Y`4=8l??+ci+>OY&cN)4+c<+Z8kYLARHxjJemp7*c{IH9sZ+ekUYv z*5}6VBCUYH}=&q|s}%k~QykH)eyT_5ET z1=kKjI7MBrbLe0O(taOGd|prfC<2;b=RoWcSu}XMnhrIV1BY62@Cn1yiuJf6TNY7N z4_HUnIW&pO3Wr{MNGwvyb^5+hvQ!^B{gbqXSvlPdr?BH|Af+Zt^&V5~U>^}K!s0i% zNijBNJVU|1r45@09tVqmT@v7@?1eMTx>fC--#*V4j!o2Pp*=_G6I=fB3it*G{CeQP zps*@4FPfiK^JY*&5z`1)2E@D8yCZ7^ys0B-z2kZ}2n}ZfdOnjm0S5cQNXISBx{5WE z0d0ysF)gisaWVyw^DIFleji-gV&aD=g2d4DWm^5Ny{*D22KCU0M}_wncyFH#?Z1HuJ-f+2lYw>o}8fo%;do_ z?~e-@VMJFV;J+D4b**uxLsLqEl(w|$b0@W;mA6qNr}S4hM1yXC-*T3*=mCJsz3NUq z(W<7}0CIW$-UAZ0$Rn>YK74GHA9-i-(m&+d%C;D@2hPTLY%emBo&{fxZA`nF#LU82 z$q@yoz{nd##w&;ST7QsN|6Gt+5o~S2>8^KZA>L^T*F9q$g?8Jv%qYzKeEvaI zj}29)?`5p#=tVtPtjAE-l()`7FIM{U9khh7?GHz3Yno)=*bsF8pJ?|DTWc17G-5iN zEzf){vbpD6Q%MoDW>ScjW5iRR1=f*7>?1qNwD@8_j?p!S5JPRH1+H>vQgXJEs09%8 z9za9uzKh8WUJs2{vTU6c0u4U9vw^WeXZyE{9l!UlIpVbf-4+)YJ^cND*jTQY6FmYO%ah#gHt_aa(E*idd=#$8r`5W~SD<`QA5@d7%HCfaJRxf~A zD>AU$=}u36Ry=ncI`p*~2zKX28b%5pY%kxDq@>IuH_{^_C`Zs+Jws%T9YGdGG^k2I zLY#AvQA3IhK4v!)_0s5*CjI-q&-eYW%cBRI5}pz=Zt0pF53OutpCdz~sEt?VPJ~Ze z)RvZ(1=Wd$M1a~<;{5JW$vC-TX(r9k$zRPxwT?~pylaP9=|tHIhqU9i3k@vpx78){-1G}gSPvp|!u3OLOV^=}SF z+q!T6B;F@W(xy`yD{c%88-34rnzFFCV1axXKu_X4PadO1=Ls>i0V#xcwS7x;K%*m% zJ9#DJ;_0x~Uh)W{KCE_mN_~-o@~mrW(Sz9iNVQHnMU$ECG0`B7!hQinrY7F^3w~ii zbwj*Sf5-@ckvj8ciIYMJP5iLROmNsA#z@=FOCf3Mn?ptGg#+I8S0o5s|WErugK)911zU zH`3^8A~U({YsvM)e;rT%(Ip3mmF`DcWoI2X4Ug~J3!O+2zRb|G8HO6wnALuF^?5s; zpq1pI7EV>g(^xi4StteQo@|xf)jyq1xKY$?5ie5&lRck3E!9`omn%HU!V0+g97{I% zyuuCo^1rjv?(*$!&_wo~Dhse5Twd3VNixU%%OG*aRvhbG$XFAP8m3JYU3ZTZ^CU^3 zyIU2SWAov4#k)L)W%QcLKnGcQC&MRgl#RwNG3)A=9=|sxgOE4+2{zt;GijBh^c$%| zsZjU;m>N6^ORvDi2s$h0%9R)b;c4ikIw_HoKXG~{+q471bL!7{g$2?Ed(vH7t{73o z-M7sZY=kQrVB^Uv?H@Q0u^*&@zsx$V&Nqp}CrOFIb*93KtdtwBjUF#Rfh94Rj*|7^ z(n9;{Ma&j57gA=VCSWq}gpV#f0axg^-wMAPa_O)%19Vr8Em>Ae*}Pe7r}zA42zuGv z?cL4&;{b~^vd{sFHT5;7E`b3$ZiDjrDK=8(FDnNU2e()JbVcl1295_I=1uf<&!7~= zKg$fVYliP*z^oAcxK=#kW*BtTzHrV-Oazc|)9K@$vubk3vQ_auM{PtJ8P9_72-6mC zDJp9}L=5H~#S3ESuDJ2kF%;|iR7$j`wEkA=1yls8(M-i&rf*DzxqIE;s;1urs%hHw z-Isp(^5|&8zf2yKR~NcLkf8c_ef0gy^I!4^Vd6f@u5FcQBHAk%h~B3A)6giuwbPNe zFOFp8Vp8eT36icDq~p9PLA+i% z?%a2D{xLeQp=!w*Nl|4B9_~_o{EICq1S*M#nT8pZwRAr&F4B&n+S(%fFzd}_O&7;L zXzal&y|9??@n}Q4Vl^WUkxMo1{!qV25k%*lt72PzVx~l)W|2mV2Nb7jt+)skA*8gaJRyh^owi1KToN4YU_HnBF^H;&opp4ZreoJqxQ|ECCIidcp zA=V3EL2G_)T&{KOpU_cbMNzBmkyz4$+^OtLVRfSWjj>wZb!~%Tgw_-KZd+A@?RwT5 zhqoj{A_tg`TR7~m-vTHD1Cp)O zMNwx$HKTLbGnpJtym zbzaolw$)X&`_2#T-$sA`NnL`WvLvui#>Hi}C1<^)vCjPc02P+LvbsuG$UP)PsT zRv^ycGF*fW-l2T~d$q$*DRbm z(p0@2uy@qfCpZ)W)6Hw?&tDS^^MP@Ly|k8K7UIOJ}t&Iceeb& z#^ji`zf!o%Pin8!+*`zi1eT&`8)OzhV7HUrq5g#k!MrQK&YYC@z_0y^|T7Rz*FU;pI zP%lg4>%qtpXJbIy-2S}kD_zZ-Sid~Ts`XS|_{;HmuTE5JTZhfOFWPU_OZxD1CF`aq z3;6d(7D-^Ldoj}<2frDrbkS>3bas~&)*Z`&V?;SxEkxw}us#W})2V;lT4L8480IMV zIL2bfU$P+D4eF6T0BfY3q0c-)U>gaE!{n;$>uejY#0o z^X$O*#Xx!P`&(^{wYZy=30)6W1RXD#+LuGPrzoB(dzfS`P;Nb#%(5P{?ydz-L9&!} z`F=0;Z}s$Z$-X+|;BW%#T2516{q;J28x+_1YnR^Q^-Q(Aod{Z2_uHC10&|smSny68Z9Zju>ddjP0FMx2 z&W)ENUX$`LEBMVl88)!fcy6MtDpwxR1kKqw<(jXA%bdCL2o8&AC}X`0Z^p!>uN%Lz z#DuIB?Xd9eKdJJWslzeHHH5|Fbx^_UcHVI@SSvgM5y~NaXD7W#!|+?t+X|eNYY0zS zOaa}@SQbrxioTW*RwBuh3O`kJ9Y<*>V8(v{qE(us*CVp+W;@sha-1&_L#t&clv&#+rw-J=G$3fz{S z1wfAw0py|x^MySROEVl=|E}4Wm#J&pvK7?(FEk?_6v$zWTu_d7zg_Q6T?Y!?+}#Ia z${bO!bz5fJ^oosDtFg5kAnV0nM*^EuKGXKQr}+leMH{;q)DeHl2VS zhcd)#H*-K7(e8P^5%0XIBSv|4Kst`(mgP0}8(Pgh%`Qw|*6Ti79khRE=*t^TXVDl) zL{b&40mjU#ZJ%{EKNcmB)4X=l!lgAnfC{u~vym`*dG%Kq619`M6AWieMbl$MfL5Ja zP1Q5ioxw7L>o z$Z?5hRe%hggpgKq>kdQPA8RuZqHW1v@e_%H6L1tAO|V)zorVuUim`3N# ze)5)ynrQY9UkWHSU!tIQ6-k^6hxF zxBIA6EX%jlq08^)9Xgd%6S>6|JuaslPkBFajH90lo@nwJz@@`)jg$b4}`g0(^a?z9`pMhUsAIKp6gwX z(ClygdLyZt()IHlWeFWS0=J7+yuhTop#KjgS4>?pT*8vrcP(wpoy74-q$1IXKe~~* z*LK%!-2|TXR9n$%-^jS=ZKqJ8yCK4N$I+62aw-z6P-~-l8VlE!mmrZpKpvH|DIa+h-Ce-yYqvc-@U)pqZK!g;opA{*vMcdgh<(!o`{6i zPpmsd8io9Q`}?<;o0 zY|Z=OzwQ`0^>g(1QIfH*6{%=Iqf{8DY(M$iU<|14$Gq=}ok>0{doaX@3h1bD_JpaU z#ep%YLXzY`%OWSMf(M_|r5Z3c8FqFb+kuyFPkrdZ39K#5H0c}#dh2|0%ikQ(nqmu5zIQSfrXPPnX0O2_{V^abrm0nbK%t<-jP|1yXM=Gu_xOTX> zM(gwR=|urXYs%BgO0@u#)X8gLrN`>n>Zn~>$;)m-!z8atP{=)6QBs6!b^l%9%l$~6 zvGmB}R7Diyq&-ih$GmocaungBa~kuq(5t0#KTe}wDME*&I5|BUXA;{HXYDd!1%o+u zNOUnQa_A>qSf6cPSWDD-_VcP7;n3wxR?8Ei6d!w46Vlx**I~e&`GxLG-oB85*pVCxIA6s4GoKn-ULeU}a?{P&i zbX%xh^L;Z@*A68)ETt7|^C?{Tv1unfJ8qn=95n`XjBp zp>Qj@c(0}Y^LtrIvbZr7RWpJ@?l*rW8^m2Dq?af+wVU16R6zHwMVY($%|5gH3oS8@{K!z710uAxKY&o7$9!n-6*Z5zZz|0kbbRre_igyxmSZ!G^5$ zx!o*`-8B3?OW`WSxBMX6&2M`iV-h61g3&6~$86Rr4|L~3#sYTBSlp$VRV{P6$+tv+$5!o?PM3IIjcbvlBw{)%{ z{6~{M^OVltZAM;{_gD^lPc$;-HGOfqUsj~9RqBBmBeA{O_dHW&Q=Q-x-#D%m(%vhU zR*Os;=D2uu%T0+<6BDzV`MvY&lPyENx!nePZXg`W<;)@32v6Gzv&pgvHKfq6mSn;! zcP^o3Shgys`ZIdgdbWytERvumTY{Jd;4jCmokb8U`bUWQgX6nX+Kc$RtvgN3{V*6P zHC)&!y4nGhg+vPFaT|Te?Go@P-sG;tz=*I327Oovt!YnN(tS1oKI`j274kh*%tyzb zTNsg~k{na$WK1EPcjcBOw?MA|1Y>uWv6ttSqnP1n>X}6-G<7*JP>MtOyiAqlEX7iO zkKn3>2fXJPW$(kPjF9JZ9A!DI7jpT%+l?kwDd9ympy4^&1eD$$-wi5zMJE=>W4f`d{&Z_}}dNqM`{qQmNIvcv>CjIlloi_!vdl%*;!s*GIQm7Z)$RzD>7 z?M)EqO(-HLq>LK&_J0kHN+LAutA03;2a``WvDRm^|4x^C7mNMoCUuZg8wsuZ;o5da z0Zry^XmHJ?Sp}XH*G?8RkzzrJg|GjakEhSwAjs{oQ)QJ0AkE3|+=+BJua1S6 zbC8{DYxf;@FXP#9EmgP__Nziet7zbOuKm8v<{Dj+QxqK_K!R_?$6 zY{-era4I{mo$a4AdPGZ6V84##Pm=)~`Cqj>R;J7Ti*h!$wp(>{tozb!Y`r3u=y)_u zt^!(Vvt#VywjWuif*Zd1N<;(M2$axHgCwG#K_LDZRjPJC|d zoYF6~)~>_8>qON&vR==s5E(Sww$PQ0!mZil?#Zm5D%FZDvT_3!9Ev_lL8W^?10%$v zZ89yUjkJ0eJ!!t6lH5lV^?g1|fAP$IeJ<9dubT5U!2_+nmTvyG*gc%FAFc;ws9QZ5 z)1SWV9;nnmSgxajsP4Sx`Bq<|6XUy2rjf zYxF~5@X2_(p>P*+d=%??#)~CDKK9m;%AYZ~s&Xzi^un%Ykl<=eME_y*jR%K;GE>9X z2!)Mr6|c_XkN@wD*95%20+sXTk-peSnQ5TPdJz}fno#}gQy(@MZf4(VBsgn)t1`USa z08IlP5?>Vl8gT`SfQWzr1B4zRfFebD z@4ZX!T|hvBbfttURhkG0Nbg0WgdPx-rc~+DJM4=)vmgF5`(eLqCT~*S%YFBrdhg9S z=jX#Ptw8GO0gvG6Gb9oipFnoB+UDX_TZF`V^Uhq|=3fX-;^g%D28Va79E@1(U7nNJ z^j)8AeIaydFqmy?UK5@_fKlYAQ@ZrWpf{ZtJvdNNoQh6qKWdJ$&VD~$VyP5^2nfo| zj{CFBO9CjHeM8&MiKV#CQWtYkv{C4Y`k@WCOnKVq6%W{$uVhw3@gB=T4)FmKPw|gk z{=f4;Od9XW389rqnOtRNzX)u}paMMM1XjQ_FhA#QYy?u7B~v4=+@g;WdmOt!0NAK8 z{?N=d^@5M7xtaG}!bB9FClFmxG?b-OLvV7GE#=zi&>EIF4vl|k-ZuKa$>YTfj$ziQ z=Sfa!d_RV^S=2Cl`zz6=W)_}z73Zbl9{v~fYwb7#+SVXKU1L-d=fUv~FGe_qN{0yv zmVZ6|D~FzKw#rP>CbncwkpDr}wD$KDQWB@LX5of3TcHXo-+NE6hr%V09Nb$AH(juB>EKU#jBUL zb5*<3R-ClkX5oKiiWdx9rATCvUTPmv^j^rz(o!wbGM)zJfhptnv6aue3;8p(CqWCM*pt!tlWNc%aj7PjYtHXvyTiXSu-iM8+sgn~SSQf!SF}usvaB7dXTP!l6IqD8dVVWB5V!pT>c*V*Xr+ z0<=YNhX#nUCSKe9vu=K@slkxY(Y{hHk8)bd+US3nh zK8toW^Qgd|I?yUM*B8*FwUcFxqAl*JQK|jd)xg{CH~|(QqclsFyyrS`_?MgzRlPLL z_3n%JvM1$KqyChOE1E;YyOsl0qn29gKnt4c^?THUR|Z71Q5*%)sHS|2A>OB!h&upk zro1|y_{h@HHTH0@l5=6dS8NO9-oKOoP?q!^5_@-Fgp?7DT z8SH}U_kHong~U#f$Ob8C=E6MQW9fwvPSH!cX+Dr3XqONDJAGqEg=h**P_9p0Ga!n7 zx+qs#wJJZrM&w~;AX7ph=lM~rfr$!%o47Q#4q4ZoE?ka%WUv3IlY&6%ilACmwYOx_ z1v4a6%g*GQ0is4xv_DCL3~8oLeBdit-x#Vus2Tliu}E;Wx-05v9PhW64&mdyqllZk z_N36~`&TfzzmpdU#_!J&A;cFcE+aGYz0#zH{A=ASuxc01f^Yd0b4JNG57>L{avKxA zH07w@mN=~DCJ5x2jlm(Dp!J}kZx9N(^hXjAPTpHA+j$gQcPWk|<}mr)@xp{~Ve873-U5Uf&ZONZAYBKyJQ5q__<5%nfhSz(vp z*puV}_a@bu_uvA}B2`E)JHHQka_r{%cZ%LBb$2md`ZW#esF2XVkt+T|VfAlcF46so zWiil3#>T`bJ+79@I)097#_QNjYANM0ohpF9%})D2aSxa}WXB`5TucmT6BZw9lp`OB zNbv-ir@em`Q%E&Z=gGn#FV18B9*+oBWtBxywI=lEWG^I1`O5=!@*WtQ3TZ6-i?5ke zJ%JVN8@t=Rzi~*+WShV~R^>6@@f!@fYv}^V9W-AtaxFWCZ~vTNCJ$+RS8S{B#0WXv z5VsZ>kVZ1#$0$eK%fYU$v;X{AR^KZ;GwF7SO6ovNPl354zcrY}OtN59MMgf;YG$WQ zRIxy%Te4;5gR2qaV~~8Gkt_(&)=I$6W;MLW*M%H>{W=s$E z^nenAqO2ILvqTfLOis+J|9`Zf+G_t-WiCf*8?5P$lQ@wFi{-CEz zcH!uHpK|zG@Jl7+YS}o>%!Co6k%BovP_;a@4y|~@<^~!COgx`qsIO5j`ED;Q`azn5{& zkcq&v^Qont0LdrbuZrs(cI6d)^ijD#gm~Yvy3rC;?QS}iJH8%J865A2uq? zuf+9p{!B;1X=F!A9SdQUA}HE6^KO0qRCc*33gv6o(k>4^8hoaXSlg&JK`PPOrAG0U z=GqZgIV`pC8Re#Mh!ZePD&wVwyPkoIX9^wc3Y{gVMUk%5UvSZ+2l>SE!L8D6Rrek=NeHbm zV;jTN4S*^IvNg+t`jk{@xq?3E)1AAqMi+w11Namw?&UNAPO)bHGZ zv_kxYr|I(4wuyh{#>ExVAT)V?rs$m36TG{A35|c9!iYMkNl3POW1Yn}^qqrdd#Gj9 zNVw|&?vy5K2_-x4^L!TeNtNpG6C^IkHCB6lxH`c;LBB$tx|G9Fw%)iKdOwzZuAtu7 zjhs#}q{Hkm#K30#7wX>u!;GiOKq0 zrajZ7aT;6fv*BSaWUPHrcW#9R`r8jwOd-(-##grMJs^Vw+bvC1(QA4FYRY-uI|g)QTi+*gvb1XZ0I*L%o0l$b>KG)V zriHk^4@y1`nS3IrMSW+zodr6OJSJm`TxHNYe#HmeuNq!9>4p;NZ|-D#<<3T~MZ*9D9DbOokemN)v>VoA8rx? z;jK*tAF$x9cz8ER?~Mp|xtN$%b>olEf#|y(trqj5x?OwIHc5Ns8X?N=i(a6emQ6ml z&MQBA2ZysInQNP!*4>UH=`UzmE!!4L-GWDI^Z>}R3aDbjAKz0j$YwN6f$j}A+Z5A5 z)spoLmwe`CkCMIg`6#%C*9`m-;#~;tt$y?Z2(quqPp5D(SeszToWEpFL;8uO(&MOlrad|e(J6hTzYoY~Z^2_o;O*=NH@o-In340sGCkJtL{m1(~% z6|Y2S{F>%)EQ=Fn(p(kroG1nRmQEBG-~Rd_RyO^)ABb?|=&M(I2}toyLy8R}{fCcZ zxzBa>Uet1MjL^iw70YvkmHxVrk|w2vk{>Cg!B~@5?$9mt`*IN@?riHbKjj{P-;c8) z=za5~Q-fN;w0Vg{w9#bQ@%2)mso{8Cai@vFx0&Bm|MTRZHRHk^XQseLf#$x@} zstTD=@;Ik@<>JMZ_e>=#o~v8j$*`UVa;{R*-!ReM{A!)bp)@jXLQEZa?1)|!(|3^l zc?M&dkt5zNmw{ijfjqB*^V89FD%?Fkya;ibdBAx5dNCD7T0XfcH}*z2q3m2badX{k zyFo3P0ao&);>Tz$ zPn=JE-2+wT33WX@XknFeG^7nov|Wlm=E#q*y(OP}DKMq3 z@?QaT`_w!Mhk-Mjc2>C2jMhgv@5tUW`UlvOL37dzBG!V?oI9K-2w4YF1MMlJ|@08iB z|3ZkV@1!1tmv^t5GV~3zTa#70#&)gO;UBHs7yF*8YimDh4wPq=@z8tC9y|EJQr<-k zC)NYT(L$wJG38ia3WlT)pB>N|4K!r(TH3zy?1_rvODJc0(HW7@?GYFQj&~F`o^H^9 zfxND_lw42+iw1u|(QNUCrlz(S+id+Jgk5URJIN@=ESLGVn6|IrLct@GuTijVbu@^y_aE@nl}*_`a$ zJjuFEYuD54?fu!<_v$x@+z1|$HE;P9=_*OoE1h_obZxCXfOV$P?aMLGVf6iXq@*s{ zKPV7S%u{pVdt^rnaV(P6g`F7!WI;aX=!tAKZLw1O3&^pAoKlaRv(Se@q9Q&GsgQX4 zqT%C5$D=Ol9{ZU_L05b;jl1~}wH~OI)o%_k0FE>NbTYhDUAnhu;f9r_lK8mIkAE^K z`{p1o2}*`&TiuxYWvqt0z+GEY*K+?{_WiJburyC%;PO)3{N!Bhj=XrUTRO(UzI@V2 zRaN!Zbc^@O?5tyh+x)FRkf?*TwSc?RH6esb)J5?T-fOGP`vVnUYa>FdHA=n1*%)i> zf`pnksk8r{(kMD=sMopLKhzJ7DE<j)>7`w#%{&?55EG6^PWt98Zvi7OQv86tuz>Yj}ZJK|BEs z)?kvUnoK7RY61brtT6x^uEDq^oQ>k4eO}4bZtcNHg4%`S-v|a1y!!c3~Nv^LPB`W+0->^HV5X)w5n5^ z>(-tN-&Ivp1G1e|R9KMdl7oYTmWK3((xvL~@Xf#p9t!>P%^!!+4A1Ku7bRLIgF9R0 z%JWygL)9YNpo~8Jzr;#BOr(+9n0GQ?EFHsxiel`tESG)OMpAU0gqd50g0r~rt&(Dn zxvp?CO2}GR1`6}Vo8?bRzR}P#td)y#d4L$~G7kx*lEIc_L;$EwCr~1Ur0I(%&>+K3RO=SaxV5(coS`cfR zGWqBeH7c^_qB9jtI_;r^7$~46IJQQE&_6nrqMmI^gHDn2Q96(zsV}Q6?)mXgYrEOw z=5Aj)k&gI=Cxa=TExu6*k#*|$+K&C2gu#q31q_`SFoof!dM@WCr@hg<^Xj;lP%gU*w=;p6QYi=CuM za)(}gkSAyfMj<35gac50@2EPAWu`&ga}i%&3&1WsjRr}-$gr>bCUr8~3#_ZLCoWEQ z9blkae*xzlYjGN7@k_N%#F&RNf%N(l_vc@%=FHyvQM;KVZ&WI`+-6x*gp~lHBDVeTyq_MbK6+0bE zK{Z*=h|YWTGMDS}fTa8kWgZUQ4Xu|&aze~A9;iLw`l;iCl!PBmEFHF>Rb8{X_ncmJ zzX1k_XZP8EI6;6StULHMYma6?f=^JPMR zC}6=(^e>0p1DSyxC-Yr0Qifrz4-xMqt8Af+UW&7_MfttZQN-ppqg?L3AFMDz9)AJN2gY zPWNMefuBf}tv5+;1LLvcu8K}Ju!I7bNr2aW^Sn^I7$1o|Tu|~pNXw|waNogiUkOYO zc9$#_)N6w#}Aeb0Z+mw^(9?N$kO5Bs9&Y|D!{2W$nQ zFPgWpY$Wu!ZsTI%=6Yi)>7GngRr|5=(V)->xHz?CEjM@qTNnj+fBti-qom_%8^DK2 zmGD}AeLiV%rB|G)ZE?BCFxe4up^LOr<}RiH!SaDLuxoYhVqGfyjgZ~oJPGgB!4cmM z=hMHHF+vW*Wpl0m71+bSebdY-G}N3^sW6#CFYjV@ZQ|-=LFU}Qkb)IncD%b#4ZJp; z*I(>*T7>L)=b2ocTwb>Q?7Vp?5%BkoaHO<%xyQ zmav{Iz~4q-Rpc!U!@Jv|!-hbbf92A2G=zdX+Y zN%%Y~cKk3d%p$cgB0cYTzUAz=lA&TYQ=GS}%c*qUwiX;3dM)3MG!NK{lX*x=$}oX# z=;|cQ2*^a1*nKMF=gNO*eEkh|VcmIC5Y1|Q_46sE)FJz|Iz{`Rsc{X~1M3?Ci@}2{ zZPayOUP|7z*y+~v$$aoG=QTmtW&P*<^Ds}F&eW4HhlNvqHES1`yz9?FXp{Luzuoyt zx7O{3qw9caDzakpPag;3j+X*4{BxGjUnUFZ^cGhe27sNs1aMSnyuH0&EYjrtdSr1% zg0pb7E-%Z?C zRhry51a)C;@5Hd zh5uGiz`&Xsbq=n^EvhodYA@p*Iy3rUQ+na2R?NCK>8#E>or?8t8D!I zj+15le|6zX9~MylzK|B1^TBW?U0};-0U|wuttFvduG_SfDCtvu`~E8`+4wocOs}VS zpon=L(EOgZO9m$$_WL$3r7rC2>zb68z=&Q^**wIJ#&)1z{T9W)AHKr}0Uuv6N)p3> z>xcyWs6DXPS#NRt!d}-m{r}fMaJ$AY>#EM{AcDwl!~Wc?ti@m5kv2>w*#0my-gkiY z)4AXP>zD*r7i`x}XAxxo`n)&5{vU&yvt#j?1#}ruFq?m@d_^Vm1^eBLgCY3g=+oJd zlevK0#`)3*SlXyGeZ-@{nesiGMo9z3Sog@?I?h&)q*?ymq|ff22Rz-1aWjMjOQ)vS z_!WRA@ciT^%_miQHLIHq`pP48BJCs99N%8tDOgC)3lB8$wH z@#B~#AiEA~{mrRMsHEy|Fz3J_ERi4mc;rO+k1wKJ|H%hY+b*r@DU|5(^78U@KRx#H zFRowJiqhi@mPAN_KJtG-LkyBC0H(O_qRm*~z*G&;!b-#|X2xqcD#z5(Fksa}R$ z99w0L$oOJ}&wdfg5OqaTNt?@N}t&IiF)d)O84vhSs4ERS3 z70(}o|737+pYuQX`o9Y6*3oIPW7dAlRC}UXt6yaaaz1eF-DSV~Wq(i(1r8c8pcRp zQdn-q510KCvG$nK+#r&Lle4~u(%lWNGdQO+omVETL4WRF0}z&oLNFk5gkfjtVc(XQ zIRKcEo9;E4n?Ri7ojJ6F%!LsI-LkxQ?_wG?-QdbxS67z<%w7Nrd!ktU|NaBw0Z1lx zalE~R#VQE|%2~GN{u7M7fuiqUM+9|~IW_OzEWL{2s;8Ax5JD5T=UF>P~ZB z2n2Al62ZqG&x?W*!Ars ztYMrimAP^RJTV%L-sop`Hi&N;o!h&6d(p><3&lupWT@88dw(C(?g=>S0zA)Yb9F#t5;PM@M z1_s>I0fUYnnd>DPYLr0UzhdA)_QGIN=wXox>kS52pR~;F7*@*zdLB^! zlPX)m;w!ME2FkXb;_b8@s6)`!_V$R87pp?29V3H-WLU4}D13Kot~DK7zNG);Dh_C6 z7U1Q-0zaKcCY@iR0Slh~djM+%3@9BnZoC8j$3&n0w~02r{=ZtablKs2se52wFn6LHg8JK

- Spacedeck is the right tool for you if you want to quickly put together a collage of your - idea or concept, either for yourself or to share it with teammembers, clients or students. + Spacedeck is a browser based application. It is the right tool for you if you want to quickly put together a collage of your idea or concept, either for yourself or to share it with teammembers, clients or students. Changes are updated in realtime.

Spacedeck is not meant for creating polished designs, but it is a good fit for: @@ -25,6 +24,7 @@

  • Shared Whiteboards
  • Design Thinking
  • + Screenshot of Spacedeck 6.0

    The hosted version of Spacedeck 6.0 is currently in beta and invite only. You can also self-host and participate in the open source development.

    -- GitLab From 9ed5a9931f7b55029bad6f6fa07eddf1b479b089 Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 8 Apr 2020 22:00:20 +0200 Subject: [PATCH 15/72] update ws module --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 59c7369..665464b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "underscore": "1.8.3", "uuid": "^3.2.1", "validator": "7.0.0", - "ws": "2.2.3" + "ws": "3.3.1" }, "main": "app.js", "description": "", -- GitLab From f5e5a7f8fecf6caf7a93c939ef28c070ecd621ba Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 8 Apr 2020 22:11:10 +0200 Subject: [PATCH 16/72] readme update --- README.md | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 10c2e3f..71e071c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,13 @@ # Spacedeck Open +![Spacedeck 6.0 Screenshot](/images/sd6-screenshot.png) + This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin Güther (magegu). The spacedeck.com online service was shut down on May 1st 2018. We decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted or local version. +[MNT Research GmbH](https://mntre.com) has restarted development of Spacedeck Open in 2020. + We appreciate filed issues, pull requests and general discussion. # Features @@ -13,9 +17,9 @@ We appreciate filed issues, pull requests and general discussion. - Write and format text with full control over fonts, colors and style - Draw, annotate and highlight with included graphical shapes - Turn your Space into a zooming presentation -- Collaborate and chat in realtime with teammates, students or friends +- Collaborate in realtime with teammates, students or friends - Share Spaces on the web or via email -- Export your work as printable PDF or ZIP +- Export your work as printable PDF or ZIP (currently being fixed, stay tuned) # Use Cases @@ -23,23 +27,13 @@ We appreciate filed issues, pull requests and general discussion. - Creative: Mood boards, Brainstorming, Design Thinking - Visual note taking and planning -# Data Import from Spacedeck.com - -Spacedeck Open has a data import feature that you can use to migrate your ZIP export from Spacedeck.com. - -1. Just copy your downloaded ZIP file into the spacedeck root folder. Don't extract it. -2. Start your local Spacedeck. -3. Navigate to *Account / Profile* (person icon in the top right corner). -4. Click the *Import* button next to the ZIP file name. It is on the bottom of the page. -5. Wait until console output has finished and you're done. - # Requirements, Installation Spacedeck requires: -- Node.js 9.x: Web Server / API. Download: https://nodejs.org +- Node.js 10.x: Web Server / API. Download: https://nodejs.org -To run Spacedeck, you only need Node.JS 9.x. +To run Spacedeck, you only need Node.JS 10.x. To install all node dependencies, run (do this once): @@ -55,10 +49,6 @@ See [config/default.json](config/default.json) Then open http://localhost:9666 in a web browser. -# Run (desktop app with integrated web server) - - electron . - # Optional Dependencies For advanced media conversion: -- GitLab From 9d3105763bdc0a005caa886aca53186f92b2b423 Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 8 Apr 2020 22:11:56 +0200 Subject: [PATCH 17/72] readme update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 71e071c..ecefb6d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Spacedeck Open -![Spacedeck 6.0 Screenshot](/images/sd6-screenshot.png) +![Spacedeck 6.0 Screenshot](/public/images/sd6-screenshot.png) This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin Güther (magegu). -- GitLab From c05afaba8ab04724cebed0ee70500faebc4e5f1e Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 14:15:48 +0200 Subject: [PATCH 18/72] remove folder and space 'duplication' leftovers --- public/javascripts/backend.js | 6 --- public/javascripts/spacedeck_spaces.js | 43 ----------------- routes/api/space_digest.js | 1 - routes/api/spaces.js | 66 +------------------------- 4 files changed, 2 insertions(+), 114 deletions(-) diff --git a/public/javascripts/backend.js b/public/javascripts/backend.js index 7970b82..47fa09e 100644 --- a/public/javascripts/backend.js +++ b/public/javascripts/backend.js @@ -141,10 +141,6 @@ function import_zip(user, filename, on_success, on_error) { load_resource("get", "/users/"+user._id+"/import?zip="+filename, null, on_success, on_error); } -function load_writable_folders(on_success, on_error) { - load_resource("get", "/spaces?writablefolders=true", null, on_success, on_error); -} - function load_history(s, on_success, on_error) { load_resource("get", "/spaces/"+ s._id +"/digest", null, on_success, on_error); } @@ -190,12 +186,10 @@ function delete_space(s, on_success, on_error) { load_resource("delete", "/spaces/"+s._id, null, on_success, on_error); } - function delete_artifact(a, on_success, on_error) { load_resource("delete", "/spaces/"+a.space_id+"/artifacts/"+a._id); } - function duplicate_space(s, to_space_id, on_success, on_error) { var path = "/spaces/"+s._id+"/duplicate"; if(to_space_id) { diff --git a/public/javascripts/spacedeck_spaces.js b/public/javascripts/spacedeck_spaces.js index 71778d7..631206d 100644 --- a/public/javascripts/spacedeck_spaces.js +++ b/public/javascripts/spacedeck_spaces.js @@ -18,8 +18,6 @@ var SpacedeckSpaces = { active_space_path: [], access_settings_space: null, access_settings_memberships: [], - duplicate_folders: [], - duplicate_folder_id: "", pending_pdf_files: [], meta_visible: false, @@ -673,47 +671,6 @@ var SpacedeckSpaces = { location.href = "/api/spaces/" + space._id + "/list"; }, - duplicate_space_into_folder: function() { - load_writable_folders( function(folders){ - this.duplicate_folders = _.sortBy(folders, function (folder) { return folder.name; }); - }.bind(this), function(xhr) { - console.error(xhr); - }); - }, - - duplicate_folder_confirm: function() { - var folderId = this.duplicate_folder_id; - var idx = _.findIndex(this.duplicate_folders, function(s) { return s._id == folderId;}); - if (idx<0) idx = 0; - var folder = this.duplicate_folders[idx]; - console.log("df f",folder); - if (!folder) return; - - duplicate_space(this.active_space, folder._id, function(new_space) { - - this.duplicate_folders = []; - this.duplicate_folder = null; - - smoke.quiz(__("duplicate_success", this.active_space.name, folder.name), function(e, test){ - if (e == __("goto_space", new_space.name)){ - this.redirect_to("/spaces/" + new_space._id); - }else if (e == __("goto_folder", folder.name)){ - this.redirect_to("/folders/" + folder._id); - } - }.bind(this), { - button_1: __("goto_space", new_space.name), - button_2: __("goto_folder", folder.name), - button_cancel:__("stay_here") - }); - - }.bind(this), function(xhr){ - - console.error(xhr); - smoke.prompt("error: " + xhr.statusText); - - }.bind(this)); - }, - toggle_follow_mode: function() { this.deselect(); this.follow_mode = !this.follow_mode; diff --git a/routes/api/space_digest.js b/routes/api/space_digest.js index cbdf490..c4cb0d4 100644 --- a/routes/api/space_digest.js +++ b/routes/api/space_digest.js @@ -138,7 +138,6 @@ router.get('/', function(req, res, next) { "$exists": 1 } }).populate("space").exec(function(err, memberships) { - async.map(memberships, function(membership, memcb) { Space.getRecursiveSubspacesForSpace(membership.space, function(err, spaces) { cb(null, spaces.map(function(s) { diff --git a/routes/api/spaces.js b/routes/api/spaces.js index 9eac2c4..b087537 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -48,70 +48,8 @@ router.get('/', function(req, res, next) { error: "auth required" }); } else { - if (req.query.writablefolders) { - db.Membership.find({where: { - user_id: req.user._id - }}, (memberships) => { - - var validMemberships = memberships.filter((m) => { - if (!m.space_id || (m.space_id == "undefined")) - return false; - return true; - }); - - var editorMemberships = validMemberships.filter((m) => { - return (m.role == "editor") || (m.role == "admin") - }); - - var spaceIds = editorMemberships.map(function(m) { - return m.space_id; - }); - - // TODO port - var q = { - "space_type": "folder", - "$or": [{ - "creator": req.user._id - }, { - "_id": { - "$in": spaceIds - }, - "creator": { - "$ne": req.user._id - } - }] - }; - - db.Space - .findAll({where: q}) - .then(function(spaces) { - var updatedSpaces = spaces.map(function(s) { - var spaceObj = s; //.toObject(); - return spaceObj; - }); - - async.map(spaces, (space, cb) => { - Space.getRecursiveSubspacesForSpace(space, (err, spaces) => { - var allSpaces = spaces; - cb(err, allSpaces); - }) - }, (err, spaces) => { - - var allSpaces = _.flatten(spaces); - - var onlyFolders = _.filter(allSpaces, (s) => { - return s.space_type == "folder"; - }) - var uniqueFolders = _.unique(onlyFolders, (s) => { - return s._id; - }) - - res.status(200).json(uniqueFolders); - }); - }); - }); - } else if (req.query.search) { - + if (req.query.search) { + db.Membership.findAll({where:{ user_id: req.user._id }}).then(memberships => { -- GitLab From 16ffecdb16f30fbde72373b8d669d73514fbcc3a Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 14:55:18 +0200 Subject: [PATCH 19/72] fix error handling and displaying on membership PUT and DELETE; don't allow to change your own role; require at least one admin --- models/db.js | 2 +- public/javascripts/spacedeck_spaces.js | 24 ++++++++++---- routes/api/space_memberships.js | 45 +++++++++++++++++++------- routes/api/spaces.js | 7 ++-- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/models/db.js b/models/db.js index cc51dac..7defdfe 100644 --- a/models/db.js +++ b/models/db.js @@ -91,7 +91,7 @@ module.exports = { user_id: Sequelize.STRING, role: Sequelize.STRING, code: Sequelize.STRING, - state: {type: Sequelize.STRING, defaultValue: "pending"}, + state: {type: Sequelize.STRING, defaultValue: "pending"}, // valid: "pending", "active" email_invited: Sequelize.STRING, created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}, updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW} diff --git a/public/javascripts/spacedeck_spaces.js b/public/javascripts/spacedeck_spaces.js index 631206d..ae70e75 100644 --- a/public/javascripts/spacedeck_spaces.js +++ b/public/javascripts/spacedeck_spaces.js @@ -776,9 +776,12 @@ var SpacedeckSpaces = { this.invite_message = ""; } }.bind(this), function(xhr){ - - text = JSON.stringify(xhr.responseText); - smoke.alert("Error: "+text); + try { + var res = JSON.parse(xhr.response); + alert("Error: "+res.error); + } catch (e) { + console.error(e, xhr); + } }.bind(this)); }.bind(this)); }, @@ -786,9 +789,13 @@ var SpacedeckSpaces = { update_member: function(space, m, role) { m.role = role; save_membership(space, m, function() { - console.log("saved") }.bind(this), function(xhr) { - console.error(xhr); + try { + var res = JSON.parse(xhr.response); + alert("Error: "+res.error); + } catch (e) { + console.error(e, xhr); + } }.bind(this)); }, @@ -797,7 +804,12 @@ var SpacedeckSpaces = { delete_membership(space, m, function() { this.access_settings_memberships.splice(this.access_settings_memberships.indexOf(m), 1); }.bind(this), function(xhr) { - console.error(xhr); + try { + var res = JSON.parse(xhr.response); + alert("Error: "+res.error); + } catch (e) { + console.error(e, xhr); + } }.bind(this)); }, diff --git a/routes/api/space_memberships.js b/routes/api/space_memberships.js index cd0f23e..4f4b85a 100644 --- a/routes/api/space_memberships.js +++ b/routes/api/space_memberships.js @@ -45,10 +45,12 @@ router.post('/', function(req, res, next) { "email": membership.email_invited }}).then(function(user) { + // existing user? then immediately activate membership if (user) { membership.user_id = user._id; membership.state = "active"; } else { + // if not, invite via email and invite code membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12); } @@ -102,11 +104,18 @@ router.put('/:membership_id', function(req, res, next) { _id: req.params.membership_id }}).then(function(mem) { if (mem) { - var attrs = req.body; - mem.role = attrs.role; - mem.save(function() { - res.status(201).json(mem); - }); + // is the user trying to change their own role? + if (mem.user_id == req.user._id) { + res.status(400).json({ + "error": "Cannot change your own role." + }); + } else { + var attrs = req.body; + mem.role = attrs.role; + mem.save(function() { + res.status(201).json(mem); + }); + } } }); } else { @@ -118,13 +127,25 @@ router.put('/:membership_id', function(req, res, next) { }); router.delete('/:membership_id', function(req, res, next) { - if (req.user) { - db.Membership.findOne({ where: { - _id: req.params.membership_id - }}).then(function(mem) { - mem.destroy().then(function() { - res.sendStatus(204); - }); + if (req.user && req.spaceRole == 'admin') { + db.Membership.count({ where: { + space_id: req.space._id, + role: "admin" + }}).then(function(adminCount) { + db.Membership.findOne({ where: { + _id: req.params.membership_id + }}).then(function(mem) { + // deleting an admin? need at least 1 + if (mem.role != "admin" || adminCount > 1) { + mem.destroy().then(function() { + res.sendStatus(204); + }); + } else { + res.status(400).json({ + "error": "Space needs at least one administrator." + }); + } + }) }); } else { res.sendStatus(403); diff --git a/routes/api/spaces.js b/routes/api/spaces.js index b087537..f3b88f1 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -168,12 +168,15 @@ router.post('/', function(req, res, next) { attrs.edit_slug = slug(attrs.name); db.Space.create(attrs).then(createdSpace => { - //if (err) res.sendStatus(400); + res.status(201).json(createdSpace); + + // create initial admin membership var membership = { _id: uuidv4(), user_id: req.user._id, space_id: attrs._id, - role: "admin" + role: "admin", + state: "active" }; db.Membership.create(membership).then(() => { -- GitLab From 4073e36441b61318531f880acf0cefdab7a224f8 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 15:02:10 +0200 Subject: [PATCH 20/72] remove noisy console.log --- models/db.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/models/db.js b/models/db.js index 7defdfe..bf4655b 100644 --- a/models/db.js +++ b/models/db.js @@ -279,8 +279,7 @@ module.exports = { getUserRoleInSpace: (originalSpace, user, cb) => { originalSpace.path = []; - console.log("getUserRoleInSpace",originalSpace._id,user._id,user.home_folder_id); - + if (originalSpace._id == user.home_folder_id || (originalSpace.creator_id && originalSpace.creator_id == user._id)) { cb("admin"); } else { -- GitLab From d6f93051effbdf46c1afe2fd285a93040a642033 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 15:21:47 +0200 Subject: [PATCH 21/72] signup: reorder fields to play better with conventions and pw managers --- views/partials/login.html | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/views/partials/login.html b/views/partials/login.html index 33b2a3c..196a32f 100644 --- a/views/partials/login.html +++ b/views/partials/login.html @@ -48,17 +48,8 @@

    [[__("signup")]]

    - -
    - -
    - -
    - -
    -
    - +
    @@ -68,9 +59,14 @@
    - +
    + +
    +
    + +
    - +
    -- GitLab From 92cf6c4397cfed0dca828027ad2b88a05942652c Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 15:22:06 +0200 Subject: [PATCH 22/72] some cleanups to mailer and user deletion --- helpers/mailer.js | 27 ---------------------- routes/api/users.js | 56 +++++++++++++++++++++++---------------------- 2 files changed, 29 insertions(+), 54 deletions(-) diff --git a/helpers/mailer.js b/helpers/mailer.js index 39a0a3c..b6d206e 100644 --- a/helpers/mailer.js +++ b/helpers/mailer.js @@ -61,33 +61,6 @@ module.exports = { } }); - } else if (config.get('mail_provider') === 'aws') { - /* - AWS.config.update({region: 'eu-west-1'}); - var ses = new AWS.SES(); - - ses.sendEmail( { - Source: from, - Destination: { ToAddresses: [to_email] }, - ReplyToAddresses: reply_to, - Message: { - Subject: { - Data: subject - }, - Body: { - Text: { - Data: plaintext, - }, - Html: { - Data: htmlText - } - } - } - }, function(err, data) { - if (err) console.error("Error sending email:", err); - else console.log("Email sent."); - }); - */ } } }; diff --git a/routes/api/users.js b/routes/api/users.js index 77bba45..233b62d 100644 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -89,28 +89,31 @@ router.post('/', function(req, res) { res.sendStatus(400); }) .then(u => { - var homeSpace = { + var homeFolder = { _id: uuidv4(), name: req.i18n.__("home"), space_type: "folder", creator_id: u._id }; - db.Space.create(homeSpace) + db.Space.create(homeFolder) .error(err => { res.sendStatus(400); }) - .then(homeSpace => { - u.home_folder_id = homeSpace._id; + .then(homeFolder => { + u.home_folder_id = homeFolder._id; u.save() .then(() => { - res.status(201).json({}); - - mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), { - action: { - link: config.endpoint + "/confirm/" + u.confirmation_token, - name: req.i18n.__("confirm_action") + // home folder created, + // auto accept pending invites + db.Membership.update({ + "state": "active" + }, { + where: { + "email_invited": u.email, + "state": pending } }); + res.status(201).json({}); }) .error(err => { res.status(400).json(err); @@ -174,36 +177,35 @@ router.post('/:id/password', function(req, res, next) { }); }); } else { - res.status(403).json({"error": "old password wrong"}); + res.status(403).json({"error": "Please enter the correct current password."}); } } else { - res.status(403).json({"error": "wrong user"}); + res.status(403).json({"error": "Access denied."}); } } else { - res.status(400).json({"error": "password_to_short"}); + res.status(400).json({"error": "Please choose a new password with at least 6 characters."}); } }); router.delete('/:id', (req, res, next) => { const user = req.user; - if(user._id == req.params.id) { - if (user.account_type == 'email') { - if (bcrypt.compareSync(req.query.password, user.password_hash)) { - user.remove((err) => { - if(err)res.status(400).json(err); - else res.sendStatus(204); - }); - } else { - res.bad_request("password_incorrect"); - } - } else { - user.remove((err) => { - if (err) res.status(400).json(err); + if (user._id == req.params.id) { + if (bcrypt.compareSync(req.query.password, user.password_hash)) { + + // TODO: this doesn't currently work. + // all objects (indirectly) belonging to the user have + // to be walked and deleted first. + + user.destroy().then(err => { + if(err)res.status(400).json(err); else res.sendStatus(204); }); + } else { + res.bad_request("Please enter the correct current password."); } + } else { + res.status(403).json({error: "Access denied."}); } - else res.status(403).json({error: ""}); }); router.put('/:user_id/confirm', (req, res) => { -- GitLab From ecdacd6e11a8c24f66412805dc6dc20125a84f64 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 15:23:14 +0200 Subject: [PATCH 23/72] explicitly make new spaces private --- routes/api/spaces.js | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/api/spaces.js b/routes/api/spaces.js index f3b88f1..7dbddd7 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -166,6 +166,7 @@ router.post('/', function(req, res, next) { attrs.creator_id = req.user._id; attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7); attrs.edit_slug = slug(attrs.name); + attrs.access_mode = "private"; db.Space.create(attrs).then(createdSpace => { res.status(201).json(createdSpace); -- GitLab From 7cf68c94a5082fb16f9a148297b0984e0abbb49c Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 15:26:28 +0200 Subject: [PATCH 24/72] remove unused templates --- views/partials/share-dialog.html | 3 --- views/partials/tool/toolbar-social.html | 29 ------------------------- 2 files changed, 32 deletions(-) delete mode 100644 views/partials/share-dialog.html delete mode 100644 views/partials/tool/toolbar-social.html diff --git a/views/partials/share-dialog.html b/views/partials/share-dialog.html deleted file mode 100644 index 156c610..0000000 --- a/views/partials/share-dialog.html +++ /dev/null @@ -1,3 +0,0 @@ -
    -

    Share this with others

    -
    diff --git a/views/partials/tool/toolbar-social.html b/views/partials/tool/toolbar-social.html deleted file mode 100644 index 8aa9015..0000000 --- a/views/partials/tool/toolbar-social.html +++ /dev/null @@ -1,29 +0,0 @@ -
    -
    - - - - - -
    -
    -- GitLab From bdb2e9fde5ffc198fecfa687c3537c4ceecc331f Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 15:37:51 +0200 Subject: [PATCH 25/72] clean up space memberships table; clean up terminate account view --- public/stylesheets/style.css | 5 ----- styles/modal.scss | 1 - views/partials/account.html | 20 ++++++++++---------- views/partials/modal/access.html | 19 ++++++++++++------- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 29ff791..c636238 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -9647,11 +9647,6 @@ button.close { outline: none; display: inline-block; text-align: left; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; border-radius: 9px; background-color: #f5f5f5 !important; box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.05), 0 2px 7px rgba(0, 0, 0, 0.1); } diff --git a/styles/modal.scss b/styles/modal.scss index cc64883..a54ba0c 100644 --- a/styles/modal.scss +++ b/styles/modal.scss @@ -135,7 +135,6 @@ outline: none; display: inline-block; text-align: left; - @include user-select(none); border-radius: $radius*3; background-color: $light !important; diff --git a/views/partials/account.html b/views/partials/account.html index ad6f875..776025b 100644 --- a/views/partials/account.html +++ b/views/partials/account.html @@ -149,27 +149,27 @@
    -
    -

    [[__("terminate_warning")]]

    -

    [[__("terminate_warning2")]]

    -
    - -
    +

    Terminate Account

    + + + - - - + +
    diff --git a/views/partials/modal/access.html b/views/partials/modal/access.html index 41abcef..9474515 100644 --- a/views/partials/modal/access.html +++ b/views/partials/modal/access.html @@ -78,21 +78,26 @@

    [[__("access_current_members")]]

    - + - - - - - {% endfor %} - - - - diff --git a/views/index.html b/views/index.ejs similarity index 90% rename from views/index.html rename to views/index.ejs index 9cea15e..6f494a1 100644 --- a/views/index.html +++ b/views/index.ejs @@ -1,9 +1,4 @@ -{% extends 'layouts/outer.html' %} - -{% block title %}Spacedeck{% endblock %} - -{% block content %} - +<%- include('layouts/outer-header') %>

    Work Together, Visually.

    @@ -30,5 +25,4 @@

    - -{% endblock %} +<%- include('layouts/outer-footer') %> diff --git a/views/layouts/outer-footer.ejs b/views/layouts/outer-footer.ejs new file mode 100644 index 0000000..cbbdcb2 --- /dev/null +++ b/views/layouts/outer-footer.ejs @@ -0,0 +1,14 @@ + + + + diff --git a/views/layouts/outer-header.ejs b/views/layouts/outer-header.ejs new file mode 100644 index 0000000..73eefac --- /dev/null +++ b/views/layouts/outer-header.ejs @@ -0,0 +1,29 @@ + + + + + Spacedeck Open + + + + + + + + +
    +
    + +
    + +
    + <% if (!user) { %> + <%=__("login")%> + <%=__("signup")%> + <% } else { %> + <%=__("spaces")%> + <%=__("logout")%> + <% } %> + +
    +
    diff --git a/views/layouts/outer.html b/views/layouts/outer.html deleted file mode 100644 index 43551fa..0000000 --- a/views/layouts/outer.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - Spacedeck Open – {% block title %}{% endblock %} - - - - - - - - - - - - - -
    -
    - -
    - -
    - {% if !user %} - [[__("login")]] - [[__("signup")]] - {% else %} - [[__("spaces")]] - [[__("logout")]] - {% endif %} - -
    -
    - - {% block content %}{% endblock %} - - - - diff --git a/views/not_found.ejs b/views/not_found.ejs new file mode 100644 index 0000000..92bac5c --- /dev/null +++ b/views/not_found.ejs @@ -0,0 +1,4 @@ + +
    +

    <%=__("not_found")%>

    +
    diff --git a/views/not_found.html b/views/not_found.html deleted file mode 100644 index aae8463..0000000 --- a/views/not_found.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends 'layouts/outer.html' %} - -{% block title %}[[ __("not_found") ]]{% endblock %} - -{% block content %} - -
    -

    [[__("not_found")]]

    -
    - -{% endblock %} diff --git a/views/partials/account.html b/views/partials/account.html index 1eba3b6..ebffe75 100644 --- a/views/partials/account.html +++ b/views/partials/account.html @@ -17,11 +17,11 @@
    -
    [[__("profile_caption")]]
    -
    [[__("language_caption")]]
    -
    [[__("notifications_caption")]]
    -
    [[__("password_caption")]]
    -
    [[__("terminate_caption")]]
    +
    <%=__("profile_caption")%>
    +
    <%=__("language_caption")%>
    +
    <%=__("notifications_caption")%>
    +
    <%=__("password_caption")%>
    +
    <%=__("terminate_caption")%>
    @@ -44,11 +44,11 @@
    -

    [[__("avatar_dimensions")]]

    +

    <%=__("avatar_dimensions")%>

    @@ -66,7 +66,7 @@
    - +
    - + - [[__('notifications_option_chat')]] + <%=__('notifications_option_chat')%>
    @@ -130,15 +130,15 @@

    Change Password

    @@ -158,14 +158,14 @@

    Terminate Account

    diff --git a/views/partials/folders.html b/views/partials/folders.html index e911aff..1069775 100644 --- a/views/partials/folders.html +++ b/views/partials/folders.html @@ -3,9 +3,9 @@ - +
    - [[ __('home') ]] + <%= __('home') %>
    -

    [[ __('no_spaces_yet') ]]

    +

    <%= __('no_spaces_yet') %>

    -

    "{{folder_spaces_filter}}"
    [[ __('search_no_results') ]]

    - +

    "{{folder_spaces_filter}}"
    <%= __('search_no_results') %>

    +
    @@ -156,8 +156,8 @@
    diff --git a/views/partials/login.html b/views/partials/login.html index de19912..f2c8b7b 100644 --- a/views/partials/login.html +++ b/views/partials/login.html @@ -4,8 +4,8 @@ @@ -21,10 +21,10 @@
    - + ">
    - + ">
    @@ -45,15 +45,15 @@
    -

    [[__("signup")]]

    +

    <%=__("signup")%>

    - + " autofocus v-focus>
    - + ">
    @@ -74,8 +74,8 @@
    {{signup_error}}
    @@ -90,11 +90,11 @@

    Password Recovery

    - + ">
    {{password_reset_error}}
    - +
    @@ -119,7 +119,7 @@
    {{password_reset_confirm_error}}
    - +
    diff --git a/views/partials/meta-folder.html b/views/partials/meta-folder.html index 9cf40b7..2b69af6 100644 --- a/views/partials/meta-folder.html +++ b/views/partials/meta-folder.html @@ -9,23 +9,23 @@
    - [[__("created_by")]] {{active_folder.creator.nickname||active_folder.creator.slug}}. -
    [[__("last_updated")]] {{active_folder.updated_at | date 'MMMM Do YYYY, HH:mm'}}. + <%=__("created_by")%> {{active_folder.creator.nickname||active_folder.creator.slug}}. +
    <%=__("last_updated")%> {{active_folder.updated_at | date 'MMMM Do YYYY, HH:mm'}}.
    Icon Email / Name Role
    - {{member.user.initials}} - + + + + - {{member.user.email}} - {{member.email_invited}} - {{member.user.nickname}} - (pending) + {{member.user.nickname}} + (pending) + ({{member.user.email}}) + ({{member.email_invited}})
    -- GitLab From 01a6bec80e3f214697be3f5edd37930a4f8a0312 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 16:20:29 +0200 Subject: [PATCH 26/72] fix loading space that user is member of --- models/db.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/db.js b/models/db.js index bf4655b..b5fb3a8 100644 --- a/models/db.js +++ b/models/db.js @@ -285,14 +285,14 @@ module.exports = { } else { var findMembershipsForSpace = function(space, allMemberships, prevRole) { Membership.findAll({ where: { - "space": space._id + "space_id": space._id }}).then(function(parentMemberships) { var currentMemberships = parentMemberships.concat(allMemberships); if (space.parent_space_id) { Space.findOne({ where: { "_id": space.parent_space_id - }}, function(err, parentSpace) { + }}).then(function(parentSpace) { findMembershipsForSpace(parentSpace, currentMemberships, prevRole); }); } else { -- GitLab From 3edde7c53c42785468546c86b175694ca4947b49 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 16:22:17 +0200 Subject: [PATCH 27/72] remove more dead code --- public/javascripts/backend.js | 8 -- public/javascripts/spacedeck_account.js | 7 - public/javascripts/spacedeck_spaces.js | 25 +--- public/javascripts/spacedeck_users.js | 6 - routes/api/spaces.js | 170 ------------------------ routes/api/users.js | 25 +--- routes/root.js | 16 +-- views/partials/account.html | 8 -- 8 files changed, 6 insertions(+), 259 deletions(-) diff --git a/public/javascripts/backend.js b/public/javascripts/backend.js index 47fa09e..b57acae 100644 --- a/public/javascripts/backend.js +++ b/public/javascripts/backend.js @@ -133,14 +133,6 @@ function load_spaces(id, is_home, on_success, on_error) { }, on_error); } -function load_importables(user, on_success, on_error) { - load_resource("get", "/users/"+user._id+"/importables", null, on_success, on_error); -} - -function import_zip(user, filename, on_success, on_error) { - load_resource("get", "/users/"+user._id+"/import?zip="+filename, null, on_success, on_error); -} - function load_history(s, on_success, on_error) { load_resource("get", "/spaces/"+ s._id +"/digest", null, on_success, on_error); } diff --git a/public/javascripts/spacedeck_account.js b/public/javascripts/spacedeck_account.js index 06f6846..8c28b42 100644 --- a/public/javascripts/spacedeck_account.js +++ b/public/javascripts/spacedeck_account.js @@ -9,19 +9,12 @@ SpacedeckAccount = { account_tab: 'invoices', password_change_error: null, feedback_text: "", - importables: [], // spacedeck.com zip import files }, methods: { show_account: function() { this.activate_dropdown('account'); }, - start_zip_import: function(f) { - if (confirm("Your archive will be imported in the background. This can take a few minutes. You can continue using Spacedeck in the meantime.")) { - import_zip(this.user, f); - } - }, - account_save_user_digest: function(val) { this.user.prefs_email_digest = val; this.save_user(function() { diff --git a/public/javascripts/spacedeck_spaces.js b/public/javascripts/spacedeck_spaces.js index ae70e75..6d37768 100644 --- a/public/javascripts/spacedeck_spaces.js +++ b/public/javascripts/spacedeck_spaces.js @@ -108,27 +108,6 @@ var SpacedeckSpaces = { space_auth = get_query_param("spaceAuth"); var userReady = function() { - if (get_query_param("embedded")) { - this.embedded = true; - this.guest_signup_enabled = true; - if (get_query_param("publish_cta")) { - this.publish_cta = get_query_param("publish_cta"); - } - if (get_query_param("nosocial")) { - this.social_bar = false; - } - } - - if (get_query_param("confirm") && this.logged_in) { - var token = get_query_param("confirm"); - confirm_user(this.user, token, function() { - this.redirect_to("/spaces/"+space_id+"?show_access=1"); - }.bind(this), function() { - alert("An error occured confirming your email with the given token."); - }); - return; - } - this.close_dropdown(); this.active_space_loaded = false; @@ -156,9 +135,7 @@ var SpacedeckSpaces = { load_space(space_id, function(space, role) { document.title = space.name; - this.active_space_role = role || "viewer"; //via req header from backend - - this.space_embed_html = ""; + this.active_space_role = role || "viewer"; // via req header from backend if (!is_home) { load_members(space, function(members) { diff --git a/public/javascripts/spacedeck_users.js b/public/javascripts/spacedeck_users.js index 3fc1d8c..1b62363 100644 --- a/public/javascripts/spacedeck_users.js +++ b/public/javascripts/spacedeck_users.js @@ -31,12 +31,6 @@ SpacedeckUsers = { if (on_success) { on_success(user); } - - // see spacedeck_account.js - load_importables(this.user, function(files) { - this.importables = files; - }.bind(this)); - }.bind(this), function() { // error this.loading_user = false; diff --git a/routes/api/spaces.js b/routes/api/spaces.js index 7dbddd7..ef36255 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -49,7 +49,6 @@ router.get('/', function(req, res, next) { }); } else { if (req.query.search) { - db.Membership.findAll({where:{ user_id: req.user._id }}).then(memberships => { @@ -304,43 +303,6 @@ router.post('/:id/background', function(req, res, next) { }); }); -var handleDuplicateSpaceRequest = function(req, res, parentSpace) { - Space.duplicateSpace(req.space, req.user, 0, (err, newSpace) => { - if (err) { - console.error(err); - res.status(400).json(err); - } else { - res.status(201).json(newSpace); - } - }, parentSpace); -} - -router.post('/:id/duplicate', (req, res, next) => { - if (req.query.parent_space_id) { - Space.findOne({ - _id: req.query.parent_space_id - }).populate('creator', userMapping).exec((err, parentSpace) => { - if (!parentSpace) { - res.status(404).json({ - "error": "parent space not found for duplicate" - }); - } else { - db.getUserRoleInSpace(parentSpace, req.user, (role) => { - if (role == "admin" ||  role == "editor") { - handleDuplicateSpaceRequest(req, res, parentSpace); - } else { - res.status(403).json({ - "error": "not authed for parent_space_id" - }); - } - }); - } - }); - } else { - handleDuplicateSpaceRequest(req, res); - } -}); - router.delete('/:id', function(req, res, next) { if (req.user) { const space = req.space; @@ -360,136 +322,4 @@ router.delete('/:id', function(req, res, next) { } }); -router.post('/:id/artifacts-pdf', function(req, res, next) { - if (req.spaceRole == "editor" || req.spaceRole == "admin") { - - var withZones = (req.query.zones) ? req.query.zones == "true" : false; - var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, ''); - var localFilePath = os.tmpdir() + "/" + fileName; - var writeStream = fs.createWriteStream(localFilePath); - var stream = req.pipe(writeStream); - - req.on('end', function() { - - var rawName = fileName.slice(0, fileName.length - 4); - var outputFolder = os.tmpdir() + "/" + rawName; - - fs.mkdir(outputFolder, function(err) { - var images = outputFolder + "/" + rawName + "-page-%03d.jpeg"; - - // FIXME not portable - exec.execFile("gs", ["-sDEVICE=jpeg", "-dDownScaleFactor=4", "-dDOINTERPOLATE", "-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-sOutputFile=" + images, "-r250", "-f", localFilePath], {}, function(error, stdout, stderr) { - if (error === null) { - - glob(outputFolder + "/*.jpeg", function(er, files) { - var count = files.length; - var delta = 10; - - var limitPerRow = Math.ceil(Math.sqrt(count)); - - var startX = parseInt(req.query.x, delta); - var startY = parseInt(req.query.y, delta); - - async.mapLimit(files, 20, function(localfilePath, cb) { - - var fileName = path.basename(localfilePath); - var baseName = path.basename(localfilePath, ".jpeg"); - - var number = parseInt(baseName.slice(baseName.length - 3, baseName.length), 10); - - gm(localFilePath).size((err, size) => { - var w = 350; - var h = w; - - var x = startX + (((number - 1) % limitPerRow) * w); - var y = startY + ((parseInt(((number - 1) / limitPerRow), 10) + 1) * w); - - var userId; - if (req.user) userId = req.user._id; - - var a = db.Artifact.create({ - _id: uuidv4(), - mime: "image/jpg", - space_id: req.space._id, - user_id: userId, - editor_name: req.guest_name, - w: w, - h: h, - x: x, - y: y, - z: (number + (count + 100)) - }).then(a => { - payloadConverter.convert(a, fileName, localfilePath, (error, artifact) => { - if (error) res.status(400).json(error); - else { - if (withZones) { - var zone = { - _id: uuidv4(), - mime: "x-spacedeck/zone", - description: "Zone " + (number), - space_id: req.space._id, - user_id: userId, - editor_name: req.guest_name, - w: artifact.w + 20, - h: artifact.h + 40, - x: x - 10, - y: y - 30, - z: number, - order: number, - valign: "middle", - align: "center" - }; - - db.Artifact.create(zone).then((z) => { - redis.sendMessage("create", "Artifact", z.toJSON(), req.channelId); - cb(null, [artifact, zone]); - }); - - } else { - cb(null, [artifact]); - } - } - }); - }); - - }); - - }, function(err, artifacts) { - - // FIXME not portable - exec.execFile("rm", ["-r", outputFolder], function(err) { - res.status(201).json(_.flatten(artifacts)); - - async.eachLimit(artifacts, 10, (artifact_or_artifacts, cb) => { - - if (artifact_or_artifacts instanceof Array) { - _.each(artifact_or_artifacts, (a) => { - redis.sendMessage("create", "Artifact", JSON.stringify(a), req.channelId); - }); - } else  { - redis.sendMessage("create", "Artifact", JSON.stringify(artifact_or_artifacts), req.channelId); - } - cb(null); - }); - }); - }); - }); - } else { - console.error("error:", error); - // FIXME not portable - exec.execFile("rm", ["-r", outputFolder], function(err) { - fs.unlink(localFilePath); - res.status(400).json({}); - }); - } - }); - }); - }); - } else { - res.status(401).json({ - "error": "no access" - }); - } -}); - module.exports = router; diff --git a/routes/api/users.js b/routes/api/users.js index 233b62d..187b925 100644 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -110,7 +110,7 @@ router.post('/', function(req, res) { }, { where: { "email_invited": u.email, - "state": pending + "state": "pending" } }); res.status(201).json({}); @@ -128,7 +128,6 @@ router.post('/', function(req, res) { db.User.findAll({where: {email: email}}) .then(users => { if (users.length == 0) { - //var domain = email.slice(email.lastIndexOf('@')+1); createUser(); } else { res.status(400).json({"error":"user_email_already_used"}); @@ -261,13 +260,6 @@ router.post('/:user_id/avatar', (req, res, next) => { }); }); -router.post('/feedback', function(req, res, next) { - var text = req.body.text; - // FIXME - mailer.sendMail("support@example.org", "Support Request by " + req.user.email, text, {reply_to: req.user.email}); - res.sendStatus(201); -}); - router.post('/password_reset_requests', (req, res, next) => { const email = req.query.email; db.User.findOne({where: {"email": email}}).then((user) => { @@ -323,19 +315,4 @@ router.post('/:user_id/confirm', function(req, res, next) { res.sendStatus(201); }); -router.get('/:user_id/importables', function(req, res, next) { - glob('*.zip', function(err, files) { - res.status(200).json(files); - }); -}); - -router.get('/:user_id/import', function(req, res, next) { - if (req.query.zip) { - res.send("importing"); - importer.importZIP(req.user, req.query.zip); - } else { - res.sendStatus(400); - } -}); - module.exports = router; diff --git a/routes/root.js b/routes/root.js index 91bb8b9..4c6deb4 100644 --- a/routes/root.js +++ b/routes/root.js @@ -54,10 +54,6 @@ router.get('/password-confirm/:token', (req, res) => { res.render('spacedeck', { title: 'Signup' }); }); -router.get('/team', (req, res) => { - res.render('spacedeck'); -}); - router.get('/de/*', (req, res) => { res.redirect("/t/de"); }); @@ -82,10 +78,6 @@ router.get('/en', (req, res) => { res.redirect("/t/end"); }); -router.get('/it', (req, res) => { - res.redirect("/t/en"); -}); - router.get('/account', (req, res) => { res.render('spacedeck'); }); @@ -132,15 +124,15 @@ router.get('/s/:token', (req, res) => { db.Space.findOne({where: {"edit_hash": token}}).then(function (space) { if (space) { if (req.accepts('text/html')){ - res.redirect("/spaces/"+space._id + "?spaceAuth=" + token); + res.redirect("/spaces/"+space._id + "?spaceAuth=" + token); } else { - res.status(200).json(space); + res.status(200).json(space); } } else { if (req.accepts('text/html')) { - res.status(404).render('not_found', { title: 'Page Not Found.' }); + res.status(404).render('not_found', { title: 'Page Not Found.' }); } else { - res.status(404).json({}); + res.status(404).json({}); } } }); diff --git a/views/partials/account.html b/views/partials/account.html index 776025b..5f94649 100644 --- a/views/partials/account.html +++ b/views/partials/account.html @@ -80,14 +80,6 @@
    - - -- GitLab From 2f39dd26beedf18999a8955da883f8d5c252c5ef Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 17:26:58 +0200 Subject: [PATCH 28/72] fix listing of invited-to-spaces in user's home folder --- routes/api/spaces.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/routes/api/spaces.js b/routes/api/spaces.js index ef36255..c21179a 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -52,7 +52,8 @@ router.get('/', function(req, res, next) { db.Membership.findAll({where:{ user_id: req.user._id }}).then(memberships => { - + // search for spaces + var validMemberships = memberships.filter(function(m) { if (!m.space_id || (m.space_id == "undefined")) return false; @@ -80,7 +81,8 @@ router.get('/', function(req, res, next) { }); } else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) { - + // list spaces in a folder + db.Space .findOne({where: { _id: req.query.parent_space_id @@ -113,14 +115,17 @@ router.get('/', function(req, res, next) { }); } else { + // list home folder and spaces/folders that the user is a member of + db.Membership.findAll({ where: { user_id: req.user._id }}).then(memberships => { if (!memberships) memberships = []; - + var validMemberships = memberships.filter(function(m) { if (!m.space_id || (m.space_id == "undefined")) return false; + return true; }); var spaceIds = validMemberships.map(function(m) { -- GitLab From 643b75ebe98b0ef00fff852b31500680e934f307 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 17:27:14 +0200 Subject: [PATCH 29/72] remove tiny piece of unused code --- middlewares/space_helpers.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/middlewares/space_helpers.js b/middlewares/space_helpers.js index 07e5931..6459529 100644 --- a/middlewares/space_helpers.js +++ b/middlewares/space_helpers.js @@ -57,11 +57,6 @@ module.exports = (req, res, next) => { "_id": spaceId }}).then(function(space) { - //.populate("creator", userMapping) - //if (err) { - // res.status(400).json(err); - //} else { - if (space) { if (space.access_mode == "public") { if (space.password) { -- GitLab From 9750f08606a9779053a1c103199169302429a04a Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 17:33:23 +0200 Subject: [PATCH 30/72] bring back copy and paste of artifacts --- public/javascripts/spacedeck_sections.js | 34 ------------------------ 1 file changed, 34 deletions(-) diff --git a/public/javascripts/spacedeck_sections.js b/public/javascripts/spacedeck_sections.js index c3a5e7e..6f1d0bc 100644 --- a/public/javascripts/spacedeck_sections.js +++ b/public/javascripts/spacedeck_sections.js @@ -2286,9 +2286,6 @@ var SpacedeckSections = { }, handle_section_paste: function(evt) { - // TODO: very confusing - return; - if (this.editing_artifact_id) return; var pastedText = null; @@ -2299,11 +2296,6 @@ var SpacedeckSections = { if (!pastedText) return; - if (!pastedText.match(/<[a-zA-Z]+>/g)) { - // crappy heuristic if this is actually HTML - pastedText = pastedText.replace(/\n/g,"
    "); - } - this.insert_embedded_artifact(pastedText); }, @@ -2350,32 +2342,6 @@ var SpacedeckSections = { this.create_artifact_via_embed_url(text); return; } - - var new_item = { - mime: "text/html", - description: text.replace("\n", "
    "), - title: "", - space_id: space._id - }; - - var w = 400; - var h = 300; - var point = this.find_place_for_item(w,h); - - new_item.x = point.x; - new_item.y = point.y; - new_item.w = w; - new_item.h = h; - new_item.z = point.z; - - if (this.guest_nickname) { - new_item.editor_name = this.guest_nickname; - } - - save_artifact(new_item, function(saved_item) { - this.update_board_artifact_viewmodel(saved_item); - this.active_space_artifacts.push(saved_item); - }.bind(this)); }, create_artifact_via_embed_url: function(url) { -- GitLab From fab2a61f83ff82b3b85dbf19bb1f219fc5eb333c Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 17:37:49 +0200 Subject: [PATCH 31/72] fix error message for messing with space memberships --- routes/api/space_memberships.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api/space_memberships.js b/routes/api/space_memberships.js index 4f4b85a..8eed84a 100644 --- a/routes/api/space_memberships.js +++ b/routes/api/space_memberships.js @@ -92,7 +92,7 @@ router.post('/', function(req, res, next) { } else { res.status(403).json({ - "error": "not_permitted" + "error": "Only administrators can do that." }); } }); -- GitLab From 6f1744bc5d82e7c192bed58c4b0f0497a425b0be Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 17:50:48 +0200 Subject: [PATCH 32/72] show share button only to admins --- views/partials/tool/toolbar-elements.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/views/partials/tool/toolbar-elements.html b/views/partials/tool/toolbar-elements.html index 88bbcac..75326f0 100644 --- a/views/partials/tool/toolbar-elements.html +++ b/views/partials/tool/toolbar-elements.html @@ -100,7 +100,9 @@ - -- GitLab From e25a56e85cee834fa8b02c38200c995770590c91 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 17:56:37 +0200 Subject: [PATCH 33/72] filter attributes on space PUT --- routes/api/spaces.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/routes/api/spaces.js b/routes/api/spaces.js index c21179a..ea41d3c 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -260,8 +260,17 @@ router.put('/:id', function(req, res) { newAttr.edit_slug = slug(newAttr['name']); delete newAttr['_id']; - delete newAttr['editor_name']; delete newAttr['creator']; + delete newAttr['creator_id']; + delete newAttr['space_type']; + + if (req['spaceRole'] != "admin") { + delete newAttr['access_mode'] + delete newAttr['password'] + delete newAttr['edit_hash'] + delete newAttr['edit_slug'] + delete newAttr['editors_locking'] + } db.Space.update(newAttr, {where: { "_id": space._id -- GitLab From 2dbfae59f936796c98fac43ca44df89c17166ad9 Mon Sep 17 00:00:00 2001 From: mntmn Date: Thu, 9 Apr 2020 17:56:47 +0200 Subject: [PATCH 34/72] remove dead functions from folder --- views/partials/folders.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/views/partials/folders.html b/views/partials/folders.html index 06e9e83..329a91a 100644 --- a/views/partials/folders.html +++ b/views/partials/folders.html @@ -113,12 +113,10 @@ -
    [[ __('home') ]]
    @@ -131,7 +129,6 @@ -

    "{{folder_spaces_filter}}"
    [[ __('search_no_results') ]]

    @@ -159,7 +156,6 @@
    diff --git a/views/spacedeck.html b/views/spacedeck.html index 426e51d..47f5837 100644 --- a/views/spacedeck.html +++ b/views/spacedeck.html @@ -85,9 +85,11 @@ window.locales.de = {}; window.locales.fr = {}; window.locales.oc = {}; + window.locales.es = {}; window.locales.en.translation = {% include "./../locales/en.js" %}; window.locales.de.translation = {% include "./../locales/de.js" %}; window.locales.fr.translation = {% include "./../locales/fr.js" %}; window.locales.oc.translation = {% include "./../locales/oc.js" %}; + window.locales.es.translation = {% include "./../locales/es.js" %}; -- GitLab From dccf0465b388b34c63a9fb20f3b9d11ceeece356 Mon Sep 17 00:00:00 2001 From: Mishkin Berteig Date: Wed, 9 Sep 2020 10:11:43 -0400 Subject: [PATCH 65/72] Fix to issue #71 Cursor positions on shared whiteboards are inconsistent (#86) * Fixed "Cursor Positions on Shared Whiteboards are Inconsistent". The main fix is in the method cursor_point_to_space in public/javascripts/spacedeck_whiteboard.js. The calculation of the coordinates of the mouse pointer from absolute window to the whiteboard space coordinates was incorrect. There were a number of dependencies on this method which were updated as a result. One side-effect was that the div for the lasso tool needed to be moved inside the div for the whiteboard. * Fixed minor panning calculation problem. Works now! --- public/javascripts/spacedeck_sections.js | 2 - public/javascripts/spacedeck_whiteboard.js | 51 ++++++---------------- views/partials/space.html | 4 +- 3 files changed, 15 insertions(+), 42 deletions(-) diff --git a/public/javascripts/spacedeck_sections.js b/public/javascripts/spacedeck_sections.js index df5daa8..540d0ae 100644 --- a/public/javascripts/spacedeck_sections.js +++ b/public/javascripts/spacedeck_sections.js @@ -797,7 +797,6 @@ var SpacedeckSections = { }, handle_user_cursor_update: function(msg) { - // console.log("handle cursor", msg); var now = new Date().getTime(); msg.t = now; var existing = false; @@ -809,7 +808,6 @@ var SpacedeckSections = { u.y = msg.y; u.t = now; u.name = msg.name; - // console.log("updated cursor "+i); existing = true; } else { // hide if no updates since 2sec diff --git a/public/javascripts/spacedeck_whiteboard.js b/public/javascripts/spacedeck_whiteboard.js index 54be6aa..7c53f14 100644 --- a/public/javascripts/spacedeck_whiteboard.js +++ b/public/javascripts/spacedeck_whiteboard.js @@ -374,12 +374,10 @@ function setup_whiteboard_directives() { var lasso_scaled = { x:this.lasso.x, y:this.lasso.y, - w:this.lasso.w*$scope.viewport_zoom, - h:this.lasso.h*$scope.viewport_zoom + w:this.lasso.w, + h:this.lasso.h } lasso_scaled = this.abs_rect(lasso_scaled); - lasso_scaled.x += $scope.bounds_margin_horiz; - lasso_scaled.y += $scope.bounds_margin_vert; var s = "left:" +lasso_scaled.x+"px;"; s += "top:" +lasso_scaled.y+"px;"; @@ -399,15 +397,15 @@ function setup_whiteboard_directives() { $("#lasso").show(); }, + // Translate the mouse cursor location from device window coordinates to virtual space coordinates cursor_point_to_space: function(evt) { var $scope = this.vm.$root; - var offset = {left: 0, top: 0}; evt = fixup_touches(evt); return { - x: (parseInt(evt.pageX) - parseInt(offset.left) - $scope.bounds_margin_horiz) / this.space_zoom, - y: (parseInt(evt.pageY) - parseInt(offset.top) - $scope.bounds_margin_vert) / this.space_zoom + x: $scope.scroll_left + (parseInt(evt.pageX) - $scope.bounds_margin_horiz) / $scope.viewport_zoom, + y: $scope.scroll_top + (parseInt(evt.pageY) - $scope.bounds_margin_vert) / $scope.viewport_zoom }; }, @@ -524,26 +522,12 @@ function setup_whiteboard_directives() { return results; }, - offset_point_in_wrapper: function(point) { - var $scope = this.vm.$root; - var section_el = $(this.el)[0]; - var z = $scope.viewport_zoom; - - var pt = parseInt($("#space").css("padding-top")); - - point.y=(point.y+section_el.scrollTop-pt)/z; - point.x=(point.x+section_el.scrollLeft)/z; - - return point; - }, - start_drawing_note: function(evt) { evt.preventDefault(); evt.stopPropagation(); var $scope = this.vm.$root; var point = this.cursor_point_to_space(evt); - this.offset_point_in_wrapper(point); var z = $scope.highest_z()+1; var a = { @@ -577,7 +561,7 @@ function setup_whiteboard_directives() { evt.stopPropagation(); var $scope = this.vm.$root; - var point = this.offset_point_in_wrapper(this.cursor_point_to_space(evt)); + var point = this.cursor_point_to_space(evt); var z = $scope.highest_z()+1; $scope.deselect(); @@ -617,7 +601,6 @@ function setup_whiteboard_directives() { var $scope = this.vm.$root; var point = this.cursor_point_to_space(evt); - this.offset_point_in_wrapper(point); var z = $scope.highest_z()+1; var a = { @@ -654,7 +637,6 @@ function setup_whiteboard_directives() { var $scope = this.vm.$root; var point = this.cursor_point_to_space(evt); - this.offset_point_in_wrapper(point); var z = $scope.highest_z()+1; var a = { @@ -697,8 +679,7 @@ function setup_whiteboard_directives() { evt.preventDefault(); if (this.mouse_state == "lasso") { - var lasso_rect = this.abs_rect(this.offset_point_in_wrapper(this.lasso)); - // convert to space coordinates + var lasso_rect = this.abs_rect(this.lasso); if (lasso_rect.w>0 && lasso_rect.h>0) { var arts = this.artifacts_in_rect(lasso_rect); @@ -777,18 +758,12 @@ function setup_whiteboard_directives() { $scope.handle_scroll(); - var cursor = this.cursor_point_to_space(evt); + var cursor = this.cursor_point_to_space(evt); // takes the raw event data and finds the mouse location in virtual space var dx = cursor.x - $scope.mouse_ox; var dy = cursor.y - $scope.mouse_oy; var dt = (new Date()).getTime() - this.last_mouse_move_time; this.last_mouse_move_time = (new Date()).getTime(); - var zoom = $scope.viewport_zoom||1; - if (zoom) { - dx/=zoom; - dy/=zoom; - } - // send cursor if (dx>10 || dy>10 || dt>100) { var name = "anonymous"; @@ -800,8 +775,8 @@ function setup_whiteboard_directives() { var cursor_msg = { action: "cursor", - x: cursor.x/zoom, - y: cursor.y/zoom, + x: cursor.x, + y: cursor.y, name: name, id: $scope.user._id||name }; @@ -971,7 +946,7 @@ function setup_whiteboard_directives() { var old_a = a; var control_points = _.cloneDeep(old_a.control_points); - var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y}); + var offset = {x:cursor.x,y:cursor.y}; control_points.push({ dx: offset.x-old_a.x, @@ -991,8 +966,8 @@ function setup_whiteboard_directives() { if (!$("#space").length) return; el = $("#space")[0]; - el.scrollLeft = this.old_panx - dx*$scope.viewport_zoom; - el.scrollTop = this.old_pany - dy*$scope.viewport_zoom; + el.scrollLeft -= dx*$scope.viewport_zoom; + el.scrollTop -= dy*$scope.viewport_zoom; $scope.handle_scroll(); } diff --git a/views/partials/space.html b/views/partials/space.html index b25db49..5880e1a 100644 --- a/views/partials/space.html +++ b/views/partials/space.html @@ -37,7 +37,7 @@ {% include "./tool/toolbar-object.html" %}
    -
    +
    @@ -79,7 +79,7 @@ 'background-color': ''+active_space.background_color, 'margin-left': bounds_margin_horiz + 'px', 'margin-top': bounds_margin_vert + 'px'}" > - +
    Date: Wed, 9 Sep 2020 16:24:30 +0200 Subject: [PATCH 66/72] wip: remove basic-auth (unused) --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 665464b..b65e005 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "dependencies": { "archiver": "1.3.0", "async": "2.3.0", - "basic-auth": "1.1.0", "bcryptjs": "2.4.3", "body-parser": "^1.19.0", "cheerio": "0.22.0", -- GitLab From a8b8e36ad32f77812a7a1555ca2dd5c6d4f1260c Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 9 Sep 2020 17:20:35 +0200 Subject: [PATCH 67/72] wip: migrate from deprecated swig templates to ejs --- docs/adding_languages.md | 12 +++--- helpers/mailer.js | 13 +----- package.json | 2 +- public/javascripts/backend.js | 3 -- routes/api/users.js | 1 - routes/root.js | 44 +++++++------------- spacedeck.js | 13 +----- views/artifact_list.html | 24 ----------- views/emails/action.html | 11 ----- views/error.ejs | 5 +++ views/error.html | 3 -- views/facebook.html | 26 ------------ views/{index.html => index.ejs} | 10 +---- views/layouts/outer-footer.ejs | 14 +++++++ views/layouts/outer-header.ejs | 29 +++++++++++++ views/layouts/outer.html | 50 ----------------------- views/not_found.ejs | 4 ++ views/not_found.html | 11 ----- views/partials/account.html | 36 ++++++++-------- views/partials/folders.html | 42 +++++++++---------- views/partials/login.html | 24 +++++------ views/partials/meta-folder.html | 16 ++++---- views/partials/meta.html | 14 +++---- views/partials/modal/access.html | 42 +++++++++---------- views/partials/modal/folder-settings.html | 8 ++-- views/partials/space.html | 10 ++--- views/partials/team.html | 30 +++++++------- views/partials/tool/background.html | 10 ++--- views/partials/tool/color.html | 8 ++-- views/partials/tool/object-options.html | 12 +++--- views/partials/tool/search.html | 2 +- views/partials/tool/shapes.html | 18 ++++---- views/partials/tool/text-formats.html | 14 +++---- views/partials/tool/toolbar-elements.html | 30 +++++++------- views/partials/tool/toolbar-object.html | 20 ++++----- views/partials/tool/toolbar-text.html | 8 ++-- views/partials/tool/zones.html | 4 +- views/public/contact.html | 10 ----- views/public/privacy.html | 9 ---- views/public/terms.html | 8 ---- views/space_list.html | 20 --------- views/{spacedeck.html => spacedeck.ejs} | 42 +++++++++---------- 42 files changed, 272 insertions(+), 440 deletions(-) delete mode 100644 views/artifact_list.html delete mode 100644 views/emails/action.html create mode 100644 views/error.ejs delete mode 100644 views/error.html delete mode 100644 views/facebook.html rename views/{index.html => index.ejs} (90%) create mode 100644 views/layouts/outer-footer.ejs create mode 100644 views/layouts/outer-header.ejs delete mode 100644 views/layouts/outer.html create mode 100644 views/not_found.ejs delete mode 100644 views/not_found.html delete mode 100644 views/public/contact.html delete mode 100644 views/public/privacy.html delete mode 100644 views/public/terms.html delete mode 100644 views/space_list.html rename views/{spacedeck.html => spacedeck.ejs} (72%) diff --git a/docs/adding_languages.md b/docs/adding_languages.md index 5972973..490323b 100644 --- a/docs/adding_languages.md +++ b/docs/adding_languages.md @@ -2,23 +2,23 @@ To add a new language to Spacedeck Open, follow these steps: -*The steps are ilustrated with Spanish (locale 'es') as the new language* +*The steps are illustrated with Spanish (locale 'es') as the new language* -- Include the new locale ('es') at the locale list (./spacedeck.js) +- Include the new locale ('es') in the locale list (./spacedeck.js): ``` locales: ["en",..., "es"], ``` -- Create the new translation file (/locales/**es.js** thar it's a copy of /locales/en.js). and translate the entries. -- Include the javascript for letting Spanish info accesible (at the end of /views/spacedeck.html) +- Create the new translation file (/locales/**es.js**, a copy of /locales/en.js) and translate the entries. +- Include the javascript for the new translation at the end of /views/spacedeck.ejs: ``` ... window.locales.es = {}; ... - window.locales.es.translation = {% include "./../locales/es.js" %}; + window.locales.es.translation = <%- include "./../locales/es.js" %>; ``` -- Include a radiobutton for users could seleect the new language (/views/partials/account.html) +- Include a radio button for users to select the new language (/views/partials/account.html) ```
    [[ a.mime ]][[ a.description | striptags ]]{% if a.payload_uri %}download{% endif %}
    @@ -102,23 +102,23 @@
    - +

    - [[__("access_no_members")]] + <%=__("access_no_members")%>

    - +
    diff --git a/views/partials/modal/folder-settings.html b/views/partials/modal/folder-settings.html index d9cad76..f2b7242 100644 --- a/views/partials/modal/folder-settings.html +++ b/views/partials/modal/folder-settings.html @@ -6,13 +6,13 @@ - + diff --git a/views/partials/space.html b/views/partials/space.html index 5880e1a..02cd2b6 100644 --- a/views/partials/space.html +++ b/views/partials/space.html @@ -14,27 +14,27 @@
    - -
    -{% include "./tool/toolbar-elements.html" %} -{% include "./tool/toolbar-object.html" %} +<%- include("./tool/toolbar-elements.html") %> +<%- include("./tool/toolbar-object.html") %>
    diff --git a/views/partials/team.html b/views/partials/team.html index c8d9977..398f010 100644 --- a/views/partials/team.html +++ b/views/partials/team.html @@ -10,24 +10,24 @@
    -

    [[__("team_name")]]

    +

    <%=__("team_name")%>

    - [[__("save")]] + <%=__("save")%>
    -

    [[__("subdomain")]]

    +

    <%=__("subdomain")%>

    - [[__("save")]] + <%=__("save")%>
    @@ -41,11 +41,11 @@

    - + "> - [[__("add")]] - ✓ [[__("invited")]] + <%=__("add")%> + ✓ <%=__("invited")%>
    @@ -53,9 +53,9 @@ - - - + + + @@ -73,14 +73,14 @@ diff --git a/views/partials/tool/background.html b/views/partials/tool/background.html index f710071..fdf15c7 100644 --- a/views/partials/tool/background.html +++ b/views/partials/tool/background.html @@ -1,8 +1,8 @@
    -
    [[__("background_image_caption")]]
    -
    [[__("background_color_caption")]]
    +
    <%=__("background_image_caption")%>
    +
    <%=__("background_color_caption")%>
    @@ -69,9 +69,9 @@
    - + " v-on="keyup: search_generic(generic_search_query) | key enter">
    diff --git a/views/partials/tool/toolbar-object.html b/views/partials/tool/toolbar-object.html index bfbc241..acaf181 100644 --- a/views/partials/tool/toolbar-object.html +++ b/views/partials/tool/toolbar-object.html @@ -1,4 +1,4 @@ -
    +'
    - +
    diff --git a/views/public/contact.html b/views/public/contact.html deleted file mode 100644 index 7e4ebe2..0000000 --- a/views/public/contact.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends '../layouts/outer.html' %} - -{% block title %}[[ __("contact") ]]{% endblock %} - -{% block content %} -
    - -
    - -{% endblock %} diff --git a/views/public/privacy.html b/views/public/privacy.html deleted file mode 100644 index 5c6a614..0000000 --- a/views/public/privacy.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends '../layouts/outer.html' %} -{% block title %}[[ __("privacy") ]]{% endblock %} - -{% block content %} - -
    - -
    -{% endblock %} diff --git a/views/public/terms.html b/views/public/terms.html deleted file mode 100644 index 42ac18f..0000000 --- a/views/public/terms.html +++ /dev/null @@ -1,8 +0,0 @@ -{% extends '../layouts/outer.html' %} - -{% block title %}[[ __("terms") ]]{% endblock %} - -{% block content %} -
    -
    -{% endblock %} diff --git a/views/space_list.html b/views/space_list.html deleted file mode 100644 index 8d2d7e0..0000000 --- a/views/space_list.html +++ /dev/null @@ -1,20 +0,0 @@ - - - -

    [[ __("folder") ]]: [[space.name]]

    -
    [[__("email")]] [[__("name")]] [[__("role")]] <%=__("email")%> <%=__("name")%> <%=__("role")%>
    - [[__("role_admin")]] - [[__("role_member")]] + <%=__("role_admin")%> + <%=__("role_member")%> - [[__("remove")]] - [[__("promote")]] - [[__("demote")]] + <%=__("remove")%> + <%=__("promote")%> + <%=__("demote")%>
    - - - - - - {% for s in subspaces %} - - - - - - {% endfor %} -
    [[__("created")]][[__("name")]][[__("link")]]
    [[ s.created_at | date('d.m.Y H:i') ]][[ s.name ]][[ s.ae_link ]]
    - - diff --git a/views/spacedeck.html b/views/spacedeck.ejs similarity index 72% rename from views/spacedeck.html rename to views/spacedeck.ejs index 47f5837..20b841f 100644 --- a/views/spacedeck.html +++ b/views/spacedeck.ejs @@ -8,17 +8,14 @@ - + - + -- GitLab From 09b42f24c2b7894a7bf5fc7e67bf5a209fd45147 Mon Sep 17 00:00:00 2001 From: mntmn Date: Wed, 16 Sep 2020 11:02:07 +0200 Subject: [PATCH 68/72] remove stray character in toolbar template --- views/partials/tool/toolbar-object.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/partials/tool/toolbar-object.html b/views/partials/tool/toolbar-object.html index acaf181..657678b 100644 --- a/views/partials/tool/toolbar-object.html +++ b/views/partials/tool/toolbar-object.html @@ -1,4 +1,4 @@ -'
    +