From bfaa542fa145d1a04dd9956a43bc207bda4eec0e Mon Sep 17 00:00:00 2001 From: "Felix W. Dekker" Date: Sat, 11 Apr 2020 11:41:44 +0200 Subject: [PATCH] Use Webpack for development --- .gitattributes | 1 + .gitignore | 104 +++++ Gruntfile.js | 97 ++++ index.html | 999 ------------------------------------------ package-lock.json | Bin 0 -> 216683 bytes package.json | 30 ++ src/main/css/main.css | 126 ++++++ src/main/index.html | 88 ++++ src/main/js/main.js | 780 +++++++++++++++++++++++++++++++++ 9 files changed, 1226 insertions(+), 999 deletions(-) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Gruntfile.js delete mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/main/css/main.css create mode 100644 src/main/index.html create mode 100644 src/main/js/main.js diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1a6bd45 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +package-lock.json binary diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ed75c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/ + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..f042e53 --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,97 @@ +const path = require('path'); + +module.exports = grunt => { + grunt.initConfig({ + pkg: grunt.file.readJSON("package.json"), + clean: { + default: ["build/"] + }, + copy: { + html: { + files: [{expand: true, cwd: "src/main/", src: "**/*.html", dest: "build/"}] + }, + css: { + files: [{expand: true, cwd: "src/main/", src: "**/*.css", dest: "build/"}] + } + }, + replace: { + dev: { + src: ["./build/*.html", "./build/*.js"], + replacements: [ + { + from: "%%VERSION_NUMBER%%", + to: "<%= pkg.version %>+" + new Date().toISOString().slice(0, 19).replace(/[-:T]/g, "") + } + ], + overwrite: true + }, + deploy: { + src: ["./build/*.html", "./build/*.js"], + replacements: [ + { + from: "%%VERSION_NUMBER%%", + to: "<%= pkg.version %>" + } + ], + overwrite: true + } + }, + webpack: { + options: { + entry: "./src/main/js/main.js", + module: { + rules: [ + { + test: /\.js$/, + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: [".js"], + }, + output: { + filename: "bundle.js", + path: path.resolve(__dirname, "build/"), + } + }, + dev: { + mode: "development", + devtool: "inline-source-map" + }, + deploy: { + mode: "production" + } + } + }); + + grunt.loadNpmTasks("grunt-contrib-clean"); + grunt.loadNpmTasks("grunt-contrib-copy"); + grunt.loadNpmTasks("grunt-text-replace"); + grunt.loadNpmTasks("grunt-webpack"); + + grunt.registerTask("dev", [ + // Pre + "clean", + // Copy files + "copy:html", + "copy:css", + // Compile + "webpack:dev", + // Post + "replace:dev" + ]); + grunt.registerTask("deploy", [ + // Pre + "clean", + // Copy files + "copy:html", + "copy:css", + // Compile JS + "webpack:deploy", + // Post + "replace:deploy" + ]); + + grunt.registerTask("default", ["dev"]); +}; diff --git a/index.html b/index.html deleted file mode 100644 index 5a78104..0000000 --- a/index.html +++ /dev/null @@ -1,999 +0,0 @@ - - - - - - - - - - - Interlanguage Checker | FWDekker - - - - - - - - - -
- -
-
-

Interlanguage Checker

- -
-

Check the consistency of MediaWiki interlanguage links in a simple overview.

-
-
-
- - - -
- -
-
-
- - - - - -
- -
-
-
-
- - -
-
-
-
-
-
-
-
- - - - -
- - - - - - - - diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..bf9a58617fa3ccd01183d26b975a5d6dc9843675 GIT binary patch literal 216683 zcmeFaX_x9cvMBhye?_hQ*fiT1FowJO_G;$Q%=3rp<$0cMjCt?f-`p?O6(L0DJDgPLZSchfBXadd>ORPfBnZlzGTDW9gQ|bpBDK%EAgz#J751-)abxF zu522x9saBMt8{s=t>KzT^ z{|$JCc3oa)v`}#|+V#LUZ+}N^Vxy@2Dx60F{|0})060@S@Ejbbk1JpIagpLryfSiwre9=Kb zyLz4pJ)Si=#5P&K55DN-E!uGbQ(eJc$qym^uj|wQint|h7=mWfo@|JD%LGKpOZ@TH zX&eh*e}-G04gm1b)4zSjS<5bAvmJL)oi)m0nUb92kXYIRH|EK9h>^Pyb{ywQ6=$w! zSVQZ!u`$~?3|QNpi_K|eUn}bKc8(kq1#8}ys|$U&V%XVB&dP%MGY8R@oaY_KgjD=X z5T1k!@SJ@08G6SoHB!tDwYA*(V&6Fq2&vfH&)K%rQV+(sGpP>^eoD-l;rv8z2IEp? z+$<~htTzl6USH~NeL`PtwAD%_Ly_~GNmXO?;FaKhfL_KI1SBDUC2k|2H^fHIpJCPB zn_9VK+LB!f8`C4Xqe^`ypgJ;Hi`&+QTETCWw{Bh0DoPLIHbYIt^pjg3AEbbhS0k5}?IqFOcb7F=HkSP+ zr&OjRpCUw8`za;!UgSh%%W#XFMh?vA>drG1xZ}#$JC$!8zFhi|Z*IvmALG9-I3fp|uMQ zJIqPlWw0oX28w^c_v<59n~;33$#V2sJwO}JvApF328*0ycr7)2PiMyDljne^(X zzxr1;r(N8&w zQuha`D&hWOU@dr~a@ZL`sc+86P0;r1(x}=N6y3%58^xB2w0L-l=O2@lct>e#&RH_9 zHq>9~+~|3cKl{hfq552`WuE6rqWo8_a%XUWr_P@~<0FpURtNrWg^7nyKlOWTttB3p zr`2jX;*XoatNP95W?C}6&?RaW*({6*dQ&KN8GNqntYa}(82BzOmG>Q6(P+}wJBGac zL5+L#eD*RkXny+7)=|tu_9YldSUiD+iEwIx?-V83E^&Su2M6R$0#Aky40Yqw7EqfO~O%qQK*~qQoTz|t1l^W)i*C< zCukYtIr%S(tX%;DBwjy%hRKfDJ2mub!4FOZRox5?YH3sy%w03KtWCMucDZrrh0+K* z+r_RcJC?fGw2SlF>~N}iV|h889n4{WWHtSz(iom5eZ62DUt%)8%z&8U2T`_|Wf$-0 zZ?#_Cn~*oVCi8NMXc=4X-p>_%$Fx3*Iil|*V}_YPfl%Y`yPkK`{7X!}3%rZ801SU7 zEbO#hGRx)NX)@^@G?yvo+BJJ&D{O5h)W$SEnP81xCoE6;HGJD@<7G>6D#)ffA$6Ws)YGg*;&R<$L&vrj9IA$MYX^fzOr``wewoN>Qe|L?5%?)C@& zD!#pdjQIBQ^cmmUuDz&FOMnMQ;a(Xxv4YYVU`umx6n2BEx(j`|d+-dbN)4;EwKHxD zfkHN>v(sSRlM70BIUg3eE;k^ib6fJ1lNkg#=I>&nKHbv%M75n6_;;B9wrTPWz|rQx z3wiqy&lD*Ad$aQ|WZ}rvz4CDM_!$!y2L+>DBb?s07tV@hcB4?k0q)rijB~3ej1Bq8 z{y30WCg*H&i&=fKR{WE(TIk$~=vwOm2>nzCsZ(%#oXV#>Z*0yR4MVsvgE?eLS3w!4gS}-lQdPGO- z8M?pOH0B|>EsR;ZJ1^`#KjhmBkqVZ(b=y1Y?zp?l*|Jxj?UoqT-z_K0DgTnpR~tAM zn7Bvub96;9qTfLmj5YyW!C!gQTUJDk{hr;};nN1zsvcJr7xScsIH}9^DcPIsTC-9y z*XZo~qd`?Zs4KPJn;9FysBEMWS(^k3ZtE+<9Amqwq$6uoN~wsV8OA~R zKhKeb{{sKM$x~7H0720QpfcdUj0sSN!Pl_9Jj0|@U)ohHu4OO{??kqg>2m1o3WX_l zI!qTjVNE)!Ryq1zr)z8tZ0#+_V`1#og?$af=N>Pq?rBxC*`+KOzJVgvbYS59Ifg2~ z%HPKj>>VNq{?4PGvf^jIuGiF3u`&)ErPNeviYAqUZpZhU#Xab3IZdw(wBX{bB`Fs?vn1ig0$#QE`SGJ4JaWkYhQ&QKsqq>$tslG1d7Mpsl*B(rJ zvm#q_%|5^E3~YA6bz0qy$k{F~uU5t0&)G}@T*^6~xAGwRM{CgJ_-83a&QMSfHC9~H z$h#iRfb2SuZU{2eWC3Lh{u5#)sq@kIqOX8NOP$Vp4h_@QP)6N93n~1HzTrD^UV#~v zU;oS;K8LhH`C9OM&$$2xqx`lOxq>+JLIe+J6VPX7j%dz>305{{xoXYA=EHJHuC`BH z8E@NMx4$ZD9BFhr)yWJmn=BUagwr;>QFBF;ImIzbhEXjwOjii@^+LduEz>BPRrwp* z1k6R{!@zX7*9a@65Gm_Y4ry`$az@$ZF^~GqinRFzmzm*#E0TPr(_TpHjxeo{{9eDb zQ5NelKO{PRSJ!!Lw40BQ%FrJxxt2#VK0Bf-V{UOM4cPTEq%^&zcIVad3Ex`uH;`ty zavmhw^2e7vR!otg%qY8pfC#%jWntg1sQWp$l&ZUM5R?`a zv(qs@H?{@0er#KtNnt9~T)T?#wese8oLJN0vDTU69W88nlXYu60YYhbn%z0db2jG? z_RCf1!Crf{fx?=a>A-wsfHkx?05)nf?w`{}sM8)Rz2Uh>?VFwFA^-^e(MP~=Q&NlVNw%2QPrnRu-staOT;;@ad*;zEf5MhjdyVMro|HcUsSHTlGY@T3RP{RI{^ zXmnm>12(&}@6orOruxIb!k@2)e&fqi)C#n_6Q~GooWri|GN%8>!2;sOUp!SQS2)Sj z0C>LZ3`grtc`p_xW0kPX;Se8a9CCIycsj#XmQn#nU^)v3AtX`{d77+LPG z*@jst_2iO59c8iMxg4i$=P$+Wir5&691(@T-yjJPCTSLC#?l!A#MAj>WNc;DOeQfm z!-_ZVmKP(eGf|uMabo_dsc>|l_xN2kL81x3sJNX^f667F*hPm?T1;S0Z;hWo2 zoR6J6Ckw($J}v_^eV>gZ0EmsFk03~saec+}xVoEh(+WoRdPaHO$2rLib*PSZ=3s$a|RL3_xup)^(@SR=PhD!Xw^4P2OqW>pIe$je{Obs@!0;{ z!9k-DWP{6t|K9j@aN5O$;h0rL(g{G<$=_fj6cn?epcJ%+=0eJKRKI2&v~UvG8M!^q5EeW@8k4uqbU;hjz`y4dXzm3k%!Tx{i!D#g7@qHSk|3EccvA zKmhtTR6_&+&Nli4R!P`hmYKqn%2uh!PCA5L*xPQ+Z^)DJa5vH`ji%N#8%>Is)CK~- zT4=^>)L0&O+;W(+HaTM}XnJ*}4!PoLr?r{giLl!;NZa1eYpl9>$sP&g?4>93#TXIx zU{LeXn1J>PIGZl<;P`bE`h*@qQp=)Nx@^eMQFT3;&BL?g*N6h__PUJ0wofdc%Xgj= z68}Yfe?>#xBQnCV0T~Bhsn_m+E!XZ77?1v#kzCl|@eL?>)BeGd~x{F&PD%kAYa1MRW`T z5EIxniB$oS$@gRwQ3q6nCU%@I3C?+M*59#4oN5pj{JFQ^p0N&Umoa>B0SM@L`G}%I zY2t_m0LSoX(7W40zYx-enL=!oL+xk~a8LFi(89w?b?qg^ioN{p&YHcv`Z^=}JzCC` zh~xg+O-8&xXaFbh7f{5Iv?;88?eq~L{K2I$tyy2G`@W$ROPEM5`Q_LSrhhz!a-kzw z=f5Hri81A6&^M)#{^s`gJ8FDIB9V7Nfe2H3y{7;Tsc)OhNb)XVA|(Sv@$v#0QJItt zp5JWt*i3M^W!Rz)O`o94_*5tZ-lLT2kcZU_|Ug^IJ2_<{Bi!37KtVI86lNI#=k5Ad;pD*@L!$M!*T%NHd}{ z4HqNsFmE@)e(|uEmz$tzbPBpj4RC2#+s%U3vNx0}eG>B*tV;2oIqx#Ws=OfUGzXjDW!Y_!r5!b@E#^>an*gllKqaVh=TzLi@*3pRmjY8uG6kVss`UtE8?(hOy)9v z^gdS=p!&}{uL1u)Yrx*oOkEzXPB5;VG2lWhE7S6EDq2=y*XkS=;0)}(JT}`Unkvs4 zvu*37uNjAKsvV_z>QOnJp{t9~ZV)YIzX|80zU!Hl9ogV!W{EQEWxcTEXD@jpz77K| z@p1zVXaYWeM&2dhAMXLKR$r_J7NR^{og*t6uFpi8dB&PL*gfY#aUEWlK|~Cd>p{mS zQ>{6U2cA0E8FEZeUnnY&gmxX0>lhk@>_&DXTxdZ;1t3J)2z2=<8z}t(wL^OVY3(f9 zWoeC#_~r|wO+bS)wsPOC!wCL+bMkRIaT$c&z@K-r7_|cJGFndwxPVm?Z=Oo!71E|H@4tpZ>EHpO(*N)|z2HKm6Oz$es_fdG3ih#7A8MiJ z^`{Iu$ZbQlcNk2IjT*CWfn2gt-)!6EtvRfe*CNT7du_eh4J)J7u2$xD({`I2Nrk<- zAA)e_pQg471BE8sDfs$l1sSn1F}zas;cY@dVM9n~j`rw(5sed|Bl;#@evh^aGv9`I zNB8PxQ<!xKXkUkrDwi3)msAdko!m?RDT_c%UEKbD zr!>Bx;ZrF`8^v*Eim%XM-zZ^1WM!T!1i(1g&wy#*PEzDz{-_+^ zZ3Hv2K|5fl?+A8+VZqaP@f!R$_$mnsVJ-n4@EHV=6^ko_r26d^w`vmN*kZe_b&WJF zd>5!>xk_0*wzKS+JGWvR3cfEXWS=OlyCoyoOT&}d*y{{89~(?%Gv_*5ppo99jQp<1UaWAUywT=A+3-0D&F1Tl2GxDN%z?B-XrzZ+i^La*gV2#}8`m*$k{Jt7E z1Aw$Se+0Yspuen4ytT#(AjRy&37ec#+!zLVloxf5Tv8?Fq#nE5MtA9LhW=uEz>lkS zjy@ghdR5^z`*EQ%Th7#6aa`OsYRViG^aNT!XXhzP>swWD-j;ffC9uPR|Ehj>S%KX{ z9KqjkLl1P76+?Yb*a_2-y6LSHR-HA~>am#{1xImM9q-s}7<6TEATEN*w7M(G;bvJD ziU)UZn3L^t$(T8l2)BBFH7JV)L6oYC39%@NFZY73j1^l?9Gc~UuC!o2HYyJN99KY( zfhm3$S8+fPYwWeoMj!2)F#fAO8o5iIT45&6v8?P;vG%3vN;YIzUWd%V(%Spz~(zZL` zF}0~~l^~otlCcp@irBjH`1pYOJCr}0X8;mIz`?;h{pMg%T1@E3~fW_|YpOdfK1 zMrEBNo4u{w^<8UaU<-|)CuEy095>y%*IrJy<3(NRndRz07;)1LOBrIRwB^euqjQ)A zyv<_%NhMB;Nx#%1ip`y&GzEr`esC#8<<#@XyWYi1DOy_y)!yoH)wi8-(+6 z7LmjAwpuAF8cyzazD;AaCg>D(*xJj2^(p~6j<%0*p5!xx^E;Io@#>2t;LJtJRJ^Y^-hUKzO#kP+KR`BFbHi>V^tsG&>!m zN!GTTF(c;I9$np3HcZj>7W?Jm*zc+XXKpn+?H>%GShQ+FI-sM2L?*3yF|1z6ZtcM} z&5VB3J;Zusk(R+J_somq(azbW#hjWPyYgWqHOBOcYgWqO$_ijaD}!9Gu*AjNU8H#W!fYa>Yi_ZxNjrF7&`iy|!5+M^_3v;F-e2QX^S(Gl^ zH>PInw!LDSL$7U;jAXZ6u<=mng@{3*}?9pTS5Mcns4PW2^nT46MR`EQz zV1O^I`Ufv4twwse$XetYWIza&@k2`ZTi@E3HE?enh&0ij?}CC8?<`{&hx)dzO#z0G z@HhsrH3EaS8N6*gFYQH>pY$1h((CS{R-*H25dI&bLJMoRUJC zmHKA@VAkhP`KY@rGlgJ!Yzr2dwp1yJjq7&J>kE9iwnvCR0B*Xhcisl3}R8Vp7fPf{r*GcYds zlaHzgDLg?I#V~LA9Nfi3Y7cgo)KaLasG;2HuYZz2k0l8cwSeJv!PoLa{D`ca$0vx%aL z7I0_7cN`Fw)^6dGRJ?%PQvjX^CshSa7NtuV`_Z*Q18`*Bp`%o_;;%}2K*n{QXd2Kc zOoe$qaR|3!FBhnyMJ=^)h*vLXq8?YY1mC?6Tc6n=uC?LR;Lm*|2eL^e#nVCUG6t(5 zFd)S6S0443BESeRgq@ZsH412O@z$O!j#Ton?~lFl*a?GeVegMZkW0_CK+QB>*bnPF zV&5G|API^)8Ja)m-Q!rF%3iy$s84c}V#VDod;DE(Fg%v9=`rhSR5Bzx<$<6h>zKL@#eEoHm?)TgYr9VwIp?)%QX9N!LXY>KMFrd#*IdnYK z>sosuR21N?=y)zSFU-NsHwS5*3p$%hsY5R&ooO(qr2|hFL~6x$SJIlVi&&x2EClkQ zCQHP4H*Me>17EfKI5x^1A5)JmXuGpi9uat+{EJA&wHw44;H)cBr7&inaC0sVQwVo8 z31FMs-|r1Xr1CHE@HjE=RSXK{CREG^Cc^pTLS!YJACQ5VDR2bsg!+)>mPQ8@h+J|6 z)@@GbN2l17g8raNH2KhJhswOC4UaRT0!-`yq;SEn%R{4ZWJZ(jL@VXOxiwkJxU!5L__mpU0vM-1Qw&yi1x;HL=kX>~1&Wpg{het0&-pe0oV2L0WfX;iGw*LZq z6?e^^DKe0Do#kwX`v5ad%fQXr^NHZA28Fxdscpeh3TkKGCh~NIz7dBWHO$1!I)6DQ z@|>5`YKvv(u)z&o@^c&3Y=$m5bsIgoE^MTaw-H%z%`KWO ztTFj6#{=N-+v0l=^(eY6J^OOXmE)y}n=3Ct-3D z3gZJ$a=zRgwu+@`XVG-agQ>X-Syowr7$7}UXEbLwB;f=|8Z>%EeLU&b*kvP-Xq_sN z?Y`Sr_d&^+i$50+yerHJD@CRo&R_KPRP$BZQi+dJ{UUcZEhP;xh~V-E;3!`9RExjJ zG&Vv`42aWTGM!*zb*dJ4i1?@P+Pirs%Hh7QanXttS4?Ptz|cOsZ7()B;&mtyR??zS zsKXcLdkT2q%7FO7+3f}y{^?@o-^~NM`Egl^cMA+6iwBjV3|3A_RY}Y_t{CAT;m^Gt znYIxbwF2!jIs<1M1F1THdb$O9?K#b1CAtM~=rxv|zCN*A#;8%0x|Ie<7-xs=4(FR? zZ|~QrCDyBqX9rA_q_9*l3Bm`JJB|Zv#P!J9Xyg`$f@4&PzpzuF!qTKHGSZ%jj;%>e_l-7YHLa#EC$>Hd1o}2 zHVwN6?k6Q%g*{d7k%uEQC7CH-SO?N<4sObLIDC4C#)M9J?Mr!}G8)_;g$`8464CLr zjaQue{ayJx7?1|HL^6c@%sf0)C65UC%D$@yhjPJRwe}~E4wuEHG@5k44ui573cqgiVQ$Df&Ckk z&I>YcQkwqe_V?HVsL4LuH;EJIo-k2b%mYJ%5cGkE3Ep3l=UMK5U>HdC2eC~j{AF0W z;{Gxn5lAfP9Z4?u4NjK=zap#5PIr8F(bn}IRXH8No?ZrB@6CDStzS-H#3_8?k$XzRt;e*X zA!r#b`Wr|<_S*wIEtg9B>p0TvdK`ohrot91i<5;N6wF#5TtH+V)~6apEbx|?D=(c^ zAW(cTt5}$$Q{bE=>23DP40n#@kqOFOISo-X?XD%*HrFpZrolbx?duqBcKw5dC6JF$ z5~J*U^gg5fC+NL_1n9jz0MSZM1iMZx4ZSUY?&9K*Y1l3`?C&*45CY8?t5i>A4NGx} z^;93>)4G(?>!re~&{~?F(ApP3b30ZoGc-8r|YihickDu=OfP5_sRl_ zb5dDkpL-k-nt%Kivcxkj7I=P9t(T^a{U``kX1Sy((K;RNy0tG2UsaWl^B@@dt4$#o zWX`Y~blyNow!C6H&Xft<$1c@*h4|V-2E+%5zd?zxetQNe2R&z9pJxKN=#I-BI$YiB z%-CkN*$j6cF6=hPg$;J=itM6QIkg(qaf!9ucB6inAFbMH(iJORY|-ltcJ*boiqR*m z@3*@3kvm>9-ACPg$Fm<1EV`uVlIH=#9Lch^SE4+U;rz8%G=Tj*3@DpTgsxA5Ag%;} z4^Zf{VINeDuQk?kO=f2YeqCDiL!~t9Q>?W-G*{n{dDD5l;{*rx?VtyoEIep}=m8Iy zz-{jgdm(tfV_LoXb`4VeYjbmP62tAGGNhVqazlMSNnHmg1yJ79bE1ME>tjMycF8(} zKyy8R23rKM%PjMSF)?r{uc}kkL#KF{tyHBrI_&jUi5MLD!DQ471~qo2`eLXS4POFv zq;vYNc(^!j@X1ssU&tMQcq++*MXfRzurxO`ac;^ z_yk!9!WmiUGt&4Ow_Zz}STBl4y=_dEr{QMZvNhAJILE=#{RVG5)J}ZPBTz)Yzv?Bb zG)A+JLkC!Z8weRB1ai!rD}h={vqsLH;LE_?D=s^8e8*}}`@*Dvg*#&*8~1)=y{}G2W>;U-Tbk4S!6TWm(*Q5hZ60Ep$yFFySU?TY+~r!= zh(K^t6Z}go*{HD>T0Gb~2KQhfcLFk0B_3gU<|Y_my$8p8uSYlEI17({e|CMD1Ql^Z z0zP1gbT2S9%X3euy9qW@J2*KzG^aLI5RXAMT%BaG4g#Hv&7vpP@J_E{It%i^wiSX~ z@0T(+E(+j6*RZkR#2PubrNuZHt+suGBZ`ydqxbiIwJ|^x5^Mx#N96A~9*@Azs7#Il zEk~IYF$fWg{-#Lnwp`yn^cuTnr`Q?~u&^AKzG079A-r4jK9BmjMum%TGW?sJsS=P7 zA`ISZIbI0rBOKiC8Oh)DH>yt7MCip8S?s^d-R50LQnl=#Jd;qR3 zL76K+b>u-|a&YG+(hLyU$@~$dL1d7ZA0@27wJ1ps)a06E3ros8IV%v$awi_PWl=wl zZ{)#MJ?H$v$OpeJ$*{$pGlRbHK1g^STAa<`x8cvd&yR9-Q?Q2GWegDhJOB5O<^P&M zc)=N|TLtGhMdOrwRh;(Hpiww_u0>0{twtVQ=IB$E$8Ku>MI&1~> ztMtjMq~A&T*ZJr)&vLJ?RfhRGNoy%H$$>=$bRvWSCw+TD`8wGhT&CTdO{s|+bgki* zs;SM*8XOnWS8PL9Jbp#b3iz_-sKe&8wG~!9aNmwOt&ObS&Mc0n9a^{OGM~e&PKzxm zGw$ddXCwLtt)LesaaAt%CfV@3#Yy!EKJ_snzgzc zxK%M&fbS}pq!s=0q}x}(77n`tXueQWe*`68+8kd<;NH1W0=R5k_zTe}J7NL3r@Nj7oLN5e~SQ8kkz z>X_h~5Z9sucw+H67r}#(N#is-7hPc>F1kMDQLkCKh+nQpPGjMbg2J>y@l-7k76n|g zzFS-`8)J2@7wqGn%TZ*V*tWRidcK(u)Un&x?j>bcX6?~##CcBFo(omIG1bBGdTaA@ z8B+$hI22sVemlFGc5v?tRWGbheOq29$*a{vHY#pEcTJoB**# zAj#-1Tj;zLf7P;aMhNcm$m&>w42DP0*%>4O3b9Gz9lQ$og9wFiD!W!+%AE{lQd}#e zT%VKL)_hO`nOZJ&>Ms`!Sy3yUVR3a>+K#`NZE_gf1QklgE+y~BhZS!ZnzrwBa$KDW z%MYLD># za2b@#1EvnxG(phQQh?B$vMEW6Q8}TAdV;a-RK0BZ47j#850cXzz%;MtoL2QIbt+Lq zSbi0C2;JpuyMUKN>>w>q0vksfN_2XzSwSZ1gHe!Ka2{bn(Rq9eyegRLkjbmk`=!$~ zXCGC*M(BFGXmFg{sg#Iy?$u>^@%0Ey;AJo+aSQYn!`YHYLy#JV9O36!h2%<_TA8sL z^$#%_eL~JzM(k}sts{L}pu|aAoKVB^Xj3`5xF-)juVDgiq2UG70hhx8{JfcJGCG}g zt~VH9y0?dD#u@!l<2V@Yag9<-SgB!A{z9L*p;-i13cf1a@)5(pqpz+9N@iSzJ4sxm zQgr@YPPv4(+A?@o9MAZ$499cy!lDtiZ~9fJcZgUB1#IYop0daww3$KG>$c`_1A1Ug znff$`w@q(%ut5o+63=QxAXgd!_;0XWv12o^D(u0v#DcOt8S~{3+%1H)$vP)?8sOmi zbWM%fVb|?ZFC*+95kyC4ui$!|$ywoebV^k|y(vKh_aMHF>k9~k@XJGX4;IhYwDGtz zUo>f^-&*;6uB_K)J;L0#tyj4~S1!8`tbhrFl0)d$hU+Qsmuv=8n15sH55OWMJiJCp z%o%qkDkpVGo#s0Z56jDO7i79OW>{B||n&pDRXWygE+!1GRyaBP zm%Irv_w5BHeqSV_O6&$nM3+n}4OELt01WnZnKh;s!*)p!H~^)Bm;32zV+J5ayULt; z#CTfqIZ#Tfehhb0zq?=6o1{efqSfS1q5~V;X%p1`{bmdRo_^U^d~t)l!Jqq(J{)CI`$Vm>AD~DF4+x(A z2V`QTX`(CKw6x=YUzqss3lsl+VPaBr(-aI6@r?VbF|`#*>`jd!K}}Q8F9r4A7beCp z!i`(k7t|pl{m}~(0Ue{Qh4>{~I|Q+CB%FEd1v*ri z*(FeITExa$PAGMQa9AV+X+`anD&AO4oPDkD4_u|sw%UZuj;UH_(5tWY(wbehj@)Q! z&b+0+3bl=1+4ogrHwXfHdURK(pWDUCQ1M;|!AwvE)S*Eg6U3uWBg5%Y(ztzkeY&$$ zzd#Nba7Zq@25U>M%kKi_-mso>WD}Gy9Um!q)T|T4B8Z!kqA+Ersc~$=aYI24P0#}Dp3m>}Nb|4a) zS;ecB<_2%ikE@>Hs@t`v?Z8!BnpA!O5 zq6XYc4(}Jcr0%9^O|CZH5yqJ&FK9=)5evQ>O_NZ1p9dnCWr8Z=?M}6(+ zO5l!HQv(xsgF(UV9on>7EVFyD*Prs@oS)XcI(Gas={G>DC1Qw(;9w68sx_qeAXg-% z2sMI9FPBt|r>rBv3<%C)qWaBGn`Ps$*Y?As+4vRv0^0F1QRdpE8dk=TEU%l2e7$1t zIoFZDvHsq{fQ)y82x<#|5R7XEjnTZ_pOK@(t^*1uhVw%p*~FNzrxIJ4Qc9}=%KnHQ ztxxR2mZ;?lhoeobdki({*dvjYgw09!t4m^!&rCtmmwl=4y|fck6*L;Zn}56alvczm zvz$U4MKCTHi=MIY>PUU!NTZ8#7=Cp)m*I1t%e$^mBMz{GB*7Kfv<(WB#gWMOQDbJ zS}^ffTi9Ys>HXy5j8+sKs10z%{JBCu%z{r{ojlsQ-l!R^C~@j02KPQ1d=ED+cW_wn z=ROATY+R^a#wgMiBB1^CGuUu~9^9jqfrbD_krw7v>IH{e5kqrEH4A(N6!#p@JV*_mTM5 zG5k<<32>CdK=%-VgBp*Ayf4t;Z@^vYhGilH;_{uFbf>?${r%n>yNoAJ|2W;hpB;*N zhjI>Qjq>)hXM_sQywTd6`Wsmg?TR;0Dg{xk2i|DRRNUDV)Teb86G3CAINi~8#~;p$ z!Q43USan@d#qdP-N}W!1Y@H5dRo|1_(MZsK&W*65BRT&SnTc^hygnrm4L3KBFV!nN zotsn-yVXVLbG`-aEVz4{7?{OxCo*#E>E>V?cqmOo!j_e76XKMRIp$kFlbo_~9w&*F%XK zN#3jb5YrhE6iqZht_*8hucL`D^z+@Y8h-}_P5%b-vojswekb1>JNN{fImghY%u>5d zbd+6d$!{BBL0)R!v@>4V!+Kv(T8nKnSLT$QErfon$_VwXv%$iOwkb`>%9`Gdr$fOS zIzPvhyzB5Fna+(0?nkwP;_EO{<>1a=hX(n&ckME=t_6ojae9OtjUtww|TkAO4UZgCvd_m z2Ucybi)&#m8MSu9dKsKbSH7?YFt5uXD(1kH<>wef>A1-Q0-0@rdoU1d_s>zLZzhKq z&nv}lX*(`AhG7M`hs)KvvuSyo8M&iBu4-{D$o_oNX;ltCnls#IjNrh-0I6O@s&vi| zIloGcdgBR4Da(3?UzUuf(ld&5S38zVL}!Y*t=^cPORY8)gnf)|6pKTpX!pm}d4UZ| zr(zY9p_+|$!ErqtxkHB%U!Mhd!XAk$;-8}mPVU>QbL=L1gtB|M$S}#g>4gc!u3281 zqS3>Khh~2ng(cT<8MsA|i%<6tT(y?+u;xutF3jK+!yUubr zT2XD&nej&pqo*_eS{Edi`<|U`{GtDtaHr%Ys6 z8Xy=QDAUTD(KUk+S7c%^)bVpQ0HX-s_87sa5qrrV^35~t^x*l1GEkIqu_sWX)TmLz z7FMM@RbM*If=`ovfw+VH7O)pgvnr1+>v%p>Xls3YJ&eOala4_KDW)(AoWYg@hwDo$ zUOsLo)RYD%7+U2?Fk5%!L0gmiFXsP|(z={EfOUnyL4`C{e$38=wLhMe>y9dL=fUw$ zO*@}|fSjK+hY_b*rYj%w;BFy66kw&~QCW0!qXz`=M6gg5;JjhpZ)(P897%yeTbx6S zW|Uk23ZA5o0h}tl)D@1dI818ec;CQ5cz5;4Bf8?828RcK;*qa(bfR_{Bc2Jkfb$Y> zfaC!vDV&Ff14opMe*U+v86GDb!sG*UzNgGH=KX{+aj-}wcyM3nEF>uDJk7g%L6;R> ztL;|K-WGPVisT=B0^AyPJZ|LK!Ls>Lf0*VNLFKO%mwJpm`Q(g`+F>c z87Wav0fl=VID*vq{+fjJG6Hf_)95k#F#tTT<-~?9uDohMTo$^~u>3ySSGi5lA? zo8hM7JLB!J&}sOBx@S$f+pQLYr9btREm1454PxnOMzPQ?t{UY9wyDq8wzL%lkS8@7 zi;J8%Ybrk4bGkMT2VAQqgvCQa*eV@OH`Q`Z zuKF5Q*=WpK!aso`PCK-PO`;d8gcPOXE|69~>u-PSSOzKd0)^nXdDFsz5NJ zJN3$OkM#ofrp(jd-2R@>Pv@J%;^e)zkD#9azD161CX2)kUBicZ%c`QrI#Zc#)%M8s z8&0v@94u5>Gb^iJPh@y?NUY?&D73+ItJG$SGkMFctjtV%9PaQkQ_kVVn^LjLVyAEDoW-pNG{L&vRC# z;+$&3UBy{CL}g61L*`)Um^=9wTn`js>AF})gO2|HrbRAq@3hI~)mPl+27DG*F4uN& zs|;KL4H@y@rv3#h=ie#I`e&woiedqa>A%l~PK%8ivu`PyH0qmeySz1rmGW978FR0# zH@jhFwA$6m+-}-#lOw6HSNFq|^7j{N-ECSvA{;h)y@Xu5Ngn-5?8;r*Ws^TZXHda| zj-Nn&FM~g!9UnnQMqFY%Ps7}oi9YB^Xvrzed>i5&-K(2TWu~t6{(2YqzTN7q7<$5k zVi!Dzdz0?J&xPLGZ;-5MMhQgq2WI*09O#*%*BY^im}ybvm&E+{xzNAIxln<2J)kK- zfef(0M#CCcaZPd0N^r9^X)R`Dp#&VjM2Ru!%OVtI{RhuTNv(||ATVuguoGn4j zYKqhCEv2Bir?`n45?u{%wmgud(weTFZh3G#;^V3k6h(DI3j(oP`CEKA;BvK7moGMQ z`$wD$YOUJao6DxBfje}1o*)t1oveU@tlE}UNta%EJ`E1uh9l}|^rXeM-5S`vV~fDWiJh^))aC-O8 zK;k|)_*Gy$qmT8fLeZiRU20qf*-5*2%cu(((c^oG#j*L|g=j)Q_51mY>2+XaqT z4%U;vx9bEBKpEp?zY$!4lwjGzg3tY@XF$GX=DlzpbtAp&-27K}FlB znKTu+WsB!cjLfMjIU`lYuPui%y&`wyZt-zu4Llf*3Q2)|v-8&Wb5)&Y%w=bqIH1e? zeMiopVLvtCQk8XK@8#Umh+GReBL^E$uAQ#W<@IiKBxR~=v{zc~SSkrj15--Myo&qS zq&Q;iVXmPA1-=-!hHGzt?UZ0gE>|3}Q2Ehp^pbVr#`u?MdBD*`tDEQe{1buHG;k!^ z5MOV5q$MYOfY>vL2&Lx-l#ZE6HRE}FwP^HK!>K-TyOvUF*S$e~+8R=m>8rE%33R;9 zrFn4QqrhWQl#u-h?0v)6nRAWWr+E<$*tw-)c4{$>4>?I`%mvbfHTRsspuR7+A@fxU+UYFpF1rGD??xI;)|}uN|j}uR{1l|BZ*I1sE7qDRlGKe-O_y0hxG~1(_v-T2PX$lq9xkJ<)%G3 z4e@0dE+6;v@7#fpR4S_W4bsP4l>PJyyE0%LK5seT!l3gp0vH-iYQHPWlSl?Lhqlp6 zV;SyJBuSfn1v5aw2n6Gd?DG?_i6A#*GM2{#HMBT!%ZWc!zA_+qCl3`x&9VnuAT)qnv|Sc;n1a@8r;m9< z-Ktvj`TAj@*RfNx@1IJ$Qz7t2D%nnEu3dZisZC2m&}31%<%cCV4le-VZy<^i53b3I zD##`roLIxC(gAPiA^}NJ8}98KSo=A!IbdsU?>PL`Oiv)XnlVHu;LGr2YbXvb3+A6*63W zk~$Ifd~R&Xta`Rv!5|WpLJch%oclt>yRK=+kdcyk7+1E6F^EQsVstkD?FAfBrw@2U zhx$=X6Up-|_dhTU?BIg+#tDCcbZEu>WjZ1z#Nj}uiN8T^T_(>KvX3^Vd*gMe(~7PZ zCIhNrRE71bzQP+`iy!vwp}CgS^08hYH3*WfkHiX7-V`WdfU&D~L9Fc>+mP5w3vJrB zcKrd_qjy;8rP=cYdp@|J=>EdJm&+d{b<+OHENfa46vAQhC9*y;qp`FvZ>7$@RI3&J zzFpW(Y^GLsc+>Ft#ms7`B*6XxV_YSW}Cb1^Gc zr&S_{?^u%j#5;a1h4)$-UjO!c6JULDc768YkST!k4j(36!jnX&Sm zK>&MvU#w$bAO>SEQ1W&ramMpAt5qo&)n#RHFsdhh53ZM^n+`ZiKmV~i5Omr~WbhPk zA?8=OH1*eq5#cF>Z*w2;Sc(X9;5YD|Gdlw^=B?{aNwvmf7vn2p13L}-oS=yILpJqy zoq-1pmQ?>QG}xo}aAn)r3jXZI*XIn27V>YSI~5+pf9fBibv>gl@OmYDJeVHTd8xEM z^^B%f^6EQ-nNdH+!kLTaWVjVq)_n*3?|d&}bb2Mt?Erf8q0$NteTi(++ z=#4te>cVtI4jXzN*(Uo{>$smY-M&j60=qtS*+qLnIn9N7EOxvNZ|f325SwOkPL_qG zyYDn*!6~el)4-lv(*sWCDok~6k)Bj|X@e#fy~Nf+;;*kwD+UB1K|uVH;0>JpG|$2~ zU!Vo?{IJ9!)n&lzJuGmEtQf)=+t>e@SPv3|dLQth8OGrc4`wt327ls)*`qlfwF2!J z|8nmY+;)i%>+{TE3aO9xKqTJS6{T@J}YW?Z4(Ri|J*FHjSqzQn8!oKX=6(Z z4qV&)3o#7%EdVzif*1~;4|y2#i7$57G2Y=BVELiw&b8einadNVdnuUL5plf6i{SRH zi-bN}=a;7NciIYrJf_4&ESK-xY~Jge$I*TNTpFXOs~^PxoRC?j8FvPJDuZ1e!7l z*Zordp4MrBxa(y9mCTAR?9gQ9af|mlyqLL_9uWHUiCMSq+fvcotXlMaOc%DRZg%<5y!n=sTVRH19 zXQeBzU&&hO==KGa(w7fp&O^KNAh8dc!s(0yzXdPH05y`Jqkx741H~`fgRc2J4blpt zDMW_OZ^%>TWDan@%73TzKCUM+@RR%(By0Q=lJ}Xng4p3NP1bm1%dfapy|)bD4(Ky} z{TD8&KrI*ta3uxGVR=6bSa-7cj4pp>8jrX~^v|}W!rFaKp790n3#yI(=Rf}eVxhl^ z|M+`kF?T{jf6Iuw;|J)yLV7>&5gd9p zRi>-pe9+^O64(L!iH9VKKW(*TXFVp1>n3{FTbbL^@A7BzN+)aJHM=0dSXJh*_D@i&YK%&iyOmjGY`11wt0RZ70 z9R!b0zySo3MT>kMB;B0_0gB^qV((e^S#bGW8+?k(he@Qaw{h)~WrXEl)hC?z=?I_D z@h4Z^cqs2uP(B^-l|63UAFUA2xcmBrX9T}|{ecwln;G?*rANyAJ!#-R&NF5IHzW=H zm@4@7Ur?Dmn*&1ki_J3htyuzvCI#RKijhOL-AwN&7X_YgXJvC^q<>@KQ#S>HqXX~Zr;VrTGIJz?RuoL{v5-emws@ zihoj-K0)4h7#(DZu)GUG`JjLfI7|-8(xY=9@8|P3-Ec(FRx1uQW-HG$8IDlR}Cjdc^c?RQ+=eK`Vg(D)3 zz4|$E-iWhe$8NE!E2*rUDl;q72hC)F0R~TcU~k?B^e&rtFGPY+oNF)MV`v4Ye#7?A zEdA!CQgH}S$>#mx8?7R#uJ&IC5ym@_uLYUOxknm=CU74NG`j`YTyEITw1c_+`C`#qxI=Kq%A^P-Tm%<5Fa&G&EkKQzjaILyC)eW<5? zE5YK8^!ka_aC5MJzk)hA=l=KdQZYh$K3#vmjy%EmpV2nO)&cxW_a30%F9RkX{1(Ul zCP6*t>wdqE|Ar%1QMG=};z=%;UC$TU$Vv42+bp00f1Hu0{8vt)7xi2O_wQBye})yZ zky+5+{;bqs?L9y|f8$4c+Jf)V1zGMtECGv=>eJ=(Yoh*(lz}8#>>QlnA$=P79wGlj zkB&(s@cY+?kwWa9DgHl@{%@Cfp>%oAxkxlUUx9*u@IE;U{Xr^urkNiYhNCZOM9JLm z_#&D6no5&|zVlC}AQSr`5&K_xmMNM%ogVd9cJs@5<$EgnZ%|-AvFx6%wG>$eJaN1c z&-WOq5;fS`ez-`B$O;o9ny?!hX;GdJ)}OC#E}{fB$k>jZ`4Bz<-|# zw%3Sw=KBF%(X6r9cOugh28>|Ox(7q^>|V%jCAmv2 zNn9M7HM}J)EwkeERRtH~p(1&^N<*&patoH;aGA!Q>w(fexp>RU_xS{G2*L$p=rigQ zP_Pc2&z$=1m;!ZON28Xnl>1ukJYscqv%u3ufFH`L<*XJs4)t%A3$49psqxuzA&A?0 z;R9GOl->oa+4iTLhj&MHtbwV+xH=GniRR#%KRZ#!KtJJFZOkoO#L2_AO$w(wu4D~V zf?1ONEokK($5C^>Do+}Xu^E+|5e*6;PR74jN7M3Azmm)x;rC1$aGL);d#01bKEXSJ za6vozj99W!1nbyWjbg`xzHpM8RyA0QZu_M1=5c9H+IhW@-;r4ol+L9x7(n|ADoY?F ztG$H4(Tv4bOAV~9h8vCEu|l1&RiD^*<93x8D82AwrUFI2a~Dbg!vFEMK2#7#^Aua& zkoE(oVrV1&G*jcem5tpYnJ|Y1S&%OE4v(}4jAQbrpSpf#8Czsu!9!U8T-lH1$>2&kp zYp8D5vo-c;*d%klynHdk2H^J&=7RA17sV?!LbJvlpRKMcVOZaq_x!N3KcCLJhg0}w zAD-qypQsayMc_zsBusj|T?sAsXwB`&ioB?&wEK9+>yx!MHXwYlGJ&A7^YQ@6F6IKReEmv-SG-uX}$BE4|LRF`xia1~OE zfz=OtJtlt8Eaw1_tx16f7}?H>j1=DMP*#%84amuXrLHZ!;bK@&4&31Ul3wq)JhCuI zC4V^<7vAJM47kPjCt!BMkat0|ldDy>T}sCn-9v;OXo{Hj#snpv#8c1?D91GT$w>zN z&jx;fcWN#ME-@|F_a(=xYhk^P1$!cv*6kC%_2amvuST)gVd&fnq}k?^DvR!N zy4g0yd})4KAKY!kE0x7wmzMKWdphQ8VY9v63$|-uzbD)T6Vw#A^s+q;+%g#rRk9Uz#q&g`ipIi=J(;dd&@5d^rh|59CL_Y;N%y`!fBp?F zHN5O2;_s_k{WrAv(mS1ufZO;bXo$1HO2N@Sbz*xMrW&w_eB#@>CQ`go(8C|VTA4!p zb-9u9HpAi15xvD_H1z0Q@t(c@=Q~nh`R`D9a=&6uPi6y_1^6#(aZe`Uzu$%F{@kwv z0X${jBe`AeL9?Wdb%dCV47T@4BY*B!ZvgTdAbfb^bU4|A2LjFgeHq^b(aCl{D@r&4 z{P!qJ-;o0vSJdBgC9s>U0MLQFfIg&Gd4XO5zQt=3qpjl7hU4~)gV$fVxJhqYe1ml{ z&we>r-e&o>kZ>{IWl?^+!A;OyoLz2zc)c3-mVRy{FX+E9O3D2@^hHYeqHC@gqCX>ymB`f9*!b% zPEU=sFVoW{UeN|+)5$eIo{0f0HSfUxDQ}=PI>6Ar>ex3LM+5F#pt%u4fXG?49=q{Z zmMa!faL|MukP__%52LBXw+CuK!@n1~zjHaUptOa+0Oi0R$Tys2vt9{4lX%(z*AKVT zv$vX#CUcQpIO3U*VrkAF_V#Qu#)L(yd!7uOb<7Waj46xoq5&4_Rd`@fiKsx!vp9Ve5>Wr8 zO|1`w#3QV`xOn^)Ic~G`Z06n{>-$b^POFXMl&ky4z3EOmORaX;&k12zqcMC#)aCWG zbl6U|(P26pwHI4Gl*swU!vkDoj@Ee0Fy?klcJ>0s=_3m_mcQ3_io5i&gy|F6w~!yq zB{b%*&7~{I=WV=tuHm)G^B4}VE(NC~-V8#rYf15RJB0<@Imb5@~F|5;$ zONS+MUNhCh*;llnxv#Tj>m=#>Im2^}^=!E9=%wnW!;N%yHD@)>9gL+J+U>LEc_Mx_lO;6M88c5DJX;xG=LZM6Zv&M5<=LQP4$GWxH4Kneb!sr zS^Y229}~Dv(O*ItkT&5Fx-b7jMM5FC z3!GPp@ih7=ZSflKeP+?;@xEo5j^~0xb()zJ$w=&l7lMI*c9(kBNwUbuP0#r~J!RP_ zR}e+H?HMc}tmiM@S|~R-X*Gv^ZLmt0opaUMsid}OVMr_bp0z+W!&FF1#M>-+Jokju zJ2D@Tc(Lx%2l{6Wra3X~YbuJdj04h5i9Edl;?692m1*4xgV3GWCTPZN`NZ1ao$9n{ z7+pW=Z+phs6E^nRw(1Vs>+dG`++XxeDQx3A;Yb*yvRf8xHSZy(PX(1Y&xvR9^<2okU}r=QQP$!YQ-U;SlwK zsU-|5PKYVvv9dIJl~Uv#*W6igR_ngsXwrhbh-GX}8Apt#Hm8ZsHf?fdneE>*kCCXj zdu6kv*R!7$cxBP0xa4oLPz|}~k8GOiR~sa;}^b6 zK0W*$TcyEeQ3ZMrL>q;f%Ow0kJTH$*O4VcgO{dE5XWCY+RswC<8buLut6!`6x!zW z7Gi1J?t5;&IPTwXAGA->z<z zQA0Lb4{B~xKXR3QojO-n7QShpR51>!-Vn#hSXZN3RBcctywTg@65Bl-wEFLD+V$`W z!vpn9YotJ^2qahDa;RXS=#BI2Crl0~PB^sUxIeP(4Y8vaCp%p*jKe) z73zCDsE?}3p=9vkp6%fDndrI0wDIzAoV%grRh5P!4+~l4%1!9VOdkwB!@&j0ZTXjH zlu49zKzLa>^iHg}eRJH@K+o)TFS<4|r{4H2*N z;?OSzGPS2CY%MU2@pMvEPoh3{msO1JayB?({F;P+ z5nT&LQ#B-3I!LH?*_ZRzg9!VGP~e?_D;ARLa+$&O+ftAX`>!mlAmc`H?@I7rXkUvc zny1;{vuqaBPv{P<(|z0w_Y?o1#<#1BQYh1UpW{p0avj^57&jNox$T`l+6`E59u+V{ zT$|wUc>;uGP%iE8WhR9}p*~DY#pgdM;2peA_>6rB4gPyPaVG$uK_sKVxPrc~3p}tD zmrp2$mL&sjGrxK#hQlt|B!!h~x<{cVTYYuY=Nt97wteA_+bnnQ#M=erh}GLPV4v;{ zQ2;l<%2GEWa~%bL-VLkkx6j|)pHiYp{^B_|-`;}ckHOrj8qFPALOpbU;WPk6>^s+f=s~6Jy zA0aq^y#rV zW>*+B*{H!HQ0BIa@76Qya9R#+hmA?Ke(ZXO$s%qxMkhLSL14-=I>PU<7`U|J!rIBM z8=_>Ui`7*`v|T-k?>8dL!&rPHpraC@AASIb@`5?r*q7f~*M`k#JZjrI!_0by#P8)P zyRL54kL`EU^~A&1QSrXOjU_Yp!~zQ8y_CW$zS<`^XxUHSfTJIny@=X`M{}thp4-(W z$51Zek;9!$;p>`9V^N?Cw+cI=M{6LI|T^hFZIf~Vx>_lt)vrdhjeSc9j5t)+o9^D!hb_U=M1p2n(92^^*RU|uh^-cz>>OW$Jbx!xiN>Qz1c5E~Q_XP!0Jk6kg-kJ! zrCz|12=R{x5;3e!cH8oRV?<|V)>vJes#aaxH)~3%-0o;TtCWZRwOgKJu6Uwm^4c?^ zvrg}&V7{?WgtkA~165oH)^XMAXv}{7InOGf10#%4=h5nGjoZSc3TKBXf@!j`zxwl-N}HjHRBlmXZ5u4##kMFrd7&31jN zOCxA=q}8Nk5J&cAD;n*LTy}#F;5CMoDC1{uI!DEvQjhGl4Y%^ zQEk}DF}9u^BjBZT93tSimdJUR>iy`Dp7uq(K_n0|U*L`tig&-4XK<-MCLZGk10nJD zgv^aX!~>ap*w#)Z_DtfX1EDyBx=qK@$Ta2$X?^xPoFa7_`voIKm06`!Zmsui-d`{5 zs^#>wdbQy@>heHVn0PZD%2-FDmQ{IrCUbS~JB)!o+%h~q=c6m_zAa95$*uncR|pnD zRKaQ#D;_&d57Kh73`fDJJMIpc#*tw#Udl7i_r@u}sY&#`!Ur*Spr98cQ;$n$4+;&2ey^RZ{x)WJWm2iDnI?$0e( z9Z%(bbzJHil(L$V=l;qZ_Qa{hjzJND!%o}}m$n$@YM3{~_u7&6`BeYfiMBZ1DF{9A zsv4N3h-8CoN$+=c*&mMEQ<~{)?7h}ih^kc9bap%qeD^F&0ctd5Su+fVk4Uz>r*i4a#wOxh!uK#W|G&xn?d(7!#ISj4100cC1T1roZSlhE|(uceajv9MIFinMjA7 z5O(C+a>V!94uga0P3NeqO;+~!2LcfKM1g9XJ0X*0oHHt_X5kgZ!#3)^DXbsLSDTonw$Z{o|ca#UDP z&}X|GUVldAHx}PLRCq1w^NiutWZ&ORxKH?uZn$6Y-y^fY%b8heB52%?V~=Ro48?=M z!#8-1Jx)baw$7`D&--CBruFJ*?pt(yyy(9O)!QupZ^Ry<6mc9bR=z#gfs;roAXixD z-(g(H{sIxudiKSTj4e1c_1&^JYS{BQvL}`$;*`o5_KLGUP6YvL<`si;V&HA}@{hCd zMDiEQegKoK{NW2Ev}VTSX~D%b|pxN{sY>SZwm>858xU-l#y-_ts0%*8RaKGEBg%GH)VbJk!- zC9+rBtZZpuuenYA2Ueu}1qz&SpD3RnA4y(7CD=>B|HQ%4#t41YQRb83u6^{yb#u23 zh)y%?WB;pk^ppFK=^U?VE&$ipgqQzSI(KOV>skHx7+!ZM7qY*Zd&SKxC^$H9JH@H2 zs9GnSlV^8o29@Ku-Cgx|cH8LUvR%f{we?}6`D!<=dt|$^qitd^aokzGI%^3wJLZ7v ztlg;?C91+X#H z13lpO#Fp{w_SusLj#4g_c4K`}$L0q{>@R$~JqNeMRkb=_i}gdLy4BZIv$A;AA-C1t z&B&YHz5ed*=7QhcpHg}msdSL6@=ft+c*gk)Q|AsPVYT}+Vzq*X&d%vBaZ^`?O@F>{ zXZDW829s0kP#)&_6>pe%UHPR_*L#x$8lZo-YzX4EX83@d_5~Mg55x8b6X|A~SW5#= z_C=c&n#Xf#`th{|z|o+flJczs;xC+2R|v^+6KKVqQ;>axLp=3twoJ`!dqz>pSQ~ED z*qaS>oSb@7Z5JEZIXVZUF{AgUTK}w1+pf<` zL<1*l#D3GvaRa==%eNc@FTMQOy_uAxP1cQg1#TkJ>$)8b_fp@EKLv z8ms=f)9B`f_MWxIH|`F=LqKp{PyiF;;Zkpy$`!M>Oz!uxCMzua!67<tEN2xlj0v)T;)3JX`*+vj}*u z9!}v&(UD(GR0q?8dw~ilRmq=3AwM|zSAn!=I0?l+LjEkQCf66%kyRPR6KYM0n(nx8 z4MgQ=(A$r;24(xbZk>pF)`@Cbqn^@h9QP%DQ)1#_Iks7)N7wbk#69k2+(K;noY~j} zmXbarEwzJ9OHJ#b60}x6tjGMIhL>5YH|38VT$zsK z$&&B?YQVuagsU$wNx3d6^ubl!@49vTRW#6r*(XV}4zS_L=i&wz_SN-Pr`o1&;uBG5 z;MJx~35`apO2_JpJc^yehG#<;KxJ zQj@nu-EVBd!&h2c-pr2FsmAxDX0D-%yy`#q)x-HO*ax3J@zJlss!X@L-3mViUXMmg zWE*{UF_$FvCx@APa4(SNWR+hk+j66o539+fRn!bESbX@eRp$b-r*x9+;!o^L_E zR*psXjx0hqKZo)!^AjC~CvUbU5RxTdF7Pwx*c-8o0cPRG)J5Vt|0 zs$AOtJ9UTVN6SQ?ic1}E)uBE+ta?Y+m$@lr&PLnDcDy(Wt9A5wTC6KZ`F8>TD(3>u zj@Af%esaugEQ0f47H#>mvJvHu;PwP>cW$~RIWNEP4Y}S-p7*>BPY^%%%p?HNI+Ks+ ztW?k|vsR~~>U25n+&gjhpdQZ#(_m3`#M#Fu>U*j5yp?8@P%kRRE&#wvFQ33d;dDjL z)X*cdlXziHoY>AGK2~=h~Mg6l!Jj4j|e)(|~=8z!k8kclk^k>5d)m z#}Zrb$`e8kY+>7A`dGX>87I--bcIOvp%Di)M!!KcQ$LHZts_5bEs}i z%08&wNcGkEhFH7R(8=A)_lR``S=6~N&*5M>*L-;fFWvl<)e%xtz^({QeGP0G0H-Kp zL_FUf)DP|5roL`Ygsv?6GBFr6n*-KoYPLJ9?JDQ_zBiWk-GN{+%3xC-MQ629B`L?{ z__97#21`#N!|9^b#Ow3|7zeq>#<$3aJ>yc`mWNa9v(d40WFXSCKD*1E|a*j=Xmsr^h3814yBt-c7p94={fD^o3?CN|Og+ zh51^E^mMU9*4L$|+ANFmaIwcRsnJvq0PHT!5 z9IGexw04teJN|~o>kT;<=b1FA2@1TU`d*{`je!Cu2l#z^I5}Ip{E|EF{594QSVO}|tZqik$&C9BOE=Dg@~*NM$VSAt z3&HG+#avtbBZ5?}$*zbZ6Aaz3``)#DufLy%?$^5fMeT>n{1D?wrGJ2QSXH6Wos(fp zwfe+VXqY=bh}uTDHO)9U2vaeyPvb{uO9F`qXIcJpr2bQlL@qZ|p21uPiuc>qX`z`6 z0X2$_;HEJMD_dL$8_!-{LK*YDdPI||I+-E##18#O=kB#|)1Eo>295~&n=46qTF{#E z#oV|ILMF%M^W9Hu4MEx4xecaLWKVwrC=6|C!@f)f-(6An1KEsp58M4 z4Z>@9NR7E@H4K#sw;Re@+vJx0xXP*H$>fC=Z?oK^>(BVQ4aiNJyNx=C9`J`>8$7?K zm)jozy150Jvf^pif$%3Nt06ch=!JDDo(8!fi@vUZb4C8`HXyW`Q$FSE*n84I_BZ$6 zKRTf_lRJDNyX)JQ)_>!5cm@Ln)Dv7_t-zHPl2aO^_Jkp}^CqCDqq5^EaEEIu=&^o=_6Fs$(YI$H z3*eEiz&=AGVC3pflcWW<4@5!G+f}iiKs$=N16Q*r(=`*-)%ipyjn-NkUQ4U zBVdjXkY*1m%xX$`M%f(r;VCZ9e4%^fqxyl5A|vuG(#02BcC_gXz{RRv$*s(o7U_(n zdX1)x=7tb-!ToAw=PCZ3W19 z@H~}B>6Bc2C+`H%knkEIdcT)hFpIOm4F=lE+f(rvKO}#mH7D?9$QVO;X19FKi>rHP zpe_gWY9=kVLw7Nh!tR`zSF0_ZGq(=I1=?Jg(skp!p3HWERco--YNf1m9PI*21nW0` zvG%ToF8iYtrT69flNQjRU8CzIS?CE~Z}JIB(FYlYgzES94Qdd0oAh}T;S})SBV%T5 zW-_X{ee(blZ0f_)+fBVec(rfN$41H4NIZ^|6>Ceo?o7n=u)DzMmjsU6EO-0n348GO zcT#_Tdcf?x{`fPx9?=BU9Ox@k|Di`LsrQNq9(Jk$bpg?AMTLDcnhW$GXrByIIIQY| z6|zfqd)9TiF4jvf*R3x5rnuDku}-fVquO?9S&RA5t4s~OTf!r4Z~A&%=`Z?~PG!0s zF8Nst%avOq~HsF8$irY~LpzKaxVrVo!#FhvAhB>6`B0r$s`Y3C6PLc!w~Km$V8N&&M0 z`(i3Q-r7hWp^2c0U%OxF9_4dgpKs|rfB#43q2N2PDZLhd`G(9H2kmkW&2oK0hWz6y zM+5W%zjm!HUS3sm{4W7iTF!U5!sO@l`L{3$ULioFUq64L8H?Z>>Z!3cYthSAZ5Rfn z)4Emhz0G#ZJ925HIS$qfDq_9S@1I+}IXfc`YPlCET!(2+2Ku;O?P$UPXBJhXaU3>- z8ODh&)jOJGu9^1HZpEDTWJwf?&Fh>hUJhOFDuGy`% zh|Ah)UMCcdRw7%i1RdR@bLHjBLi3JLw-XPPk7?pem_pP+Gyz#}b;#a->-4xleBLey z3I2OlbJ=f%#ufB-Kwx0&;1493vpnqxFod1f|8V{@fA#gHX3-r+o=DovFS? zByR8Um(~NKQIL=BaNalfs}m2!3#}AqiYt^WXI!5@i)dJp;-JWJ0-@`q3qU= zkt;=QKdNqM%iAnD*2F;<*il5a&g;c|(ydayJ_iRSX}hz_GBxT^beY5SUPqrt^;Td1 zJ)v}uP5k*6RBZ)?lJ&(M#iVoQH}!V_iT^mT0lb=zaL`6PeS@xn6t2O>kIqrUn3{{m zqBB`gJ^e_OB6=+L_Cb#~r%qps^x27QYXMuYjX}%%X`N?#Y?nFlXIwaAt)5XjF(dL^ z?u=GvLU!Crn14^;h5B+O6}Z3#uAknHk!eAcX(mDxeXp2d_Ey?&R8%qFAY9OoprH(| zLJ3XSR~Xx5o7}HhQ179qGQ>LOK=pZj+~M;Kp|`6ArtmlB->X#pWUXNS%Syqg;=B&J zg#gy|SJdI?)E28DUp=$~VO*)%z5d5pNf76O;wt&+&``qt)X=>_xU3aHL#r)l-V(Fe zY_36;v)+k39;2G8W<@;cvl5=Ss~<5$a_G4s;_3pwQ3lj0_Y>#i;YmN#6X(ve5`I*^ zs8rAoPYKEN5OW9)K-&jpha$yWl$dA+3dybKM`Jw_9C=!ag2O_z6l<}PKy};h(CP!Z zG&y;-g*KWDJN=s9>;kE@ANst?m+G5pspfPiI}4N{lFtYARpCAys6Imat*kqeL+>0G zz?4XeQK3;VY?EzTaT3TqLAUDBKxobv(=a0RDZk%695o<%Sy(aUF6waWL>(ARaD4?% zbI?$-*(%@1o7URi(ObPk)dVYb*BFOj5=tv$>*M>{D}k9H^Agr>I8 z*if8S*fTY!dgoPytCB^G7nocAJw*-~`dKF@7jHB7MOQ%l=S#W<3tq(tWUW8e7Ys$d z1OkfiAMl)lQW#Qx+EdmswXmeVLP)kUfmS>h2m6{^=F#u&v|))JoQbHU5XU*Di{)sW zmsk8kad`?IWPTa2sC2Rs>Ff@KVvvWAZqZ)S@E^FT5SGm_mrH7>-O6af#%*=%m+c-m8i9&firJF#Ts|+an1@z@(?v}Htu4Mlf49V$ z`z~$_gdiHNoUe7C-o(#d+)Gzw^ZaIL&mgBIM#$COpEU^Fngs#0Yo^ev4UDETOh@N5 z@OB^$AgGXi;r;mw6};)fcF~HUraN%vprLP1wvf8MU=3IQo*j@W4;&FzB}J2E85QVV zL99>zGQ?0*4Add|ilHKe`WS3}FVtbs`lV4_VS#Xi0jc)(025ciq(z7q3i&Ccs7H-7 z$Z0Js@s_)*4aDZ86p~_(tks$ktu=?Oc|A}%$~jz}&q}WwZ_Z{lavaU18hES2ucfBn zY6i4zdtPTf*9GDClrzX62hJerv29NV?In5UzM?d&)bYBj1&U0H#v* z5E=sxC1A&)9Wt$gH^%F8jw)P`5rv>%dgV8i?-Od>Q69+~XRY zbpUI*1n4WE+JEL6Km(2Y4IXHn6f>6;O@py*4pdwM8}y#VvP4C34!Z+EJa&J4>w3y` zIbm2`n#jR^Yct_s!MC;8@S^Tk+IA$3>`W$We-zlYV|8AhM@qA$fW{iFLC{&92dhe; zY(_$9TMzV_Xp3x&+;q_NzybdDo4_m-@qIA5-fz%fxpFU7 z9on44!28UVJGc3{S~t5(v9)h9EE69zpB#)%XOh_-y2F6er)`1NeS5PztyN=Cc5Rml zM9(eW zmOC7(ycW&$1Bkdb+a1?o+Ia;Va6|(?3}&W}#%tl|jzn(a zdW-UA-q~7x*Qw8CR+iagLn_(csOc;F(qa>-9Y#0~S&%yV=~YZ{l5pXFmX%*c8`5f8 zVHKy<8w`Z5+tb^;l1$|u;RLpFx2KIq(Vh?-aOynnl4`GADp%Jc14^77!wSC_LX0Q1 zDqCB!^`Olhd!;eA=XQFNSh?l0GcXt@_N|U{oCNwx{XKdWKnZvb^2z$pm4->(a`G6^jlo22Hcu2zlGnh+vZ=wK#d*i=VB2W-%78{43Bix+6l>dbXPPi^ez zsbr%?o`df15j*UUjSd9KSv`#^wB5kPmmJzI#oUsiX@fSU6TZ)kOHcbO-yvl9@7W=r z?LFvL+bf8FBSJKW>r&p5vg z$koTUQDEhd#ceJ^1X?mr>~|f1XT$7o?!SLB)K$>6@jDj%3+o9@57`raK>JtNZVI;c zcE60J8Z(KiEBR2dM`Yv&_OTU@7n7D^57%AKW-8`zb2^)~g}CS(8WKY)y>qQ1O2p2! z%BI5DyzHLEKEqVkjsE!un%fLOoE#{fYpT{a0_~Lt3JbI}B#5~5cW-&%h4wKJcMDrA zX~y_u%rzzIAa&jIa1(`t`mcG&Ip9^|6MrYvAekN!!L;Hu1`{Ebtz$t2{|5#81@c6| zQ5exbJSR4@RCq#PXiNoI1DUS@yq^x2INqTBm_89ew)q2YG<*S{+r{e!!PTDG?SXmF zAAB!IZ?E>f0EMrfHk7a2T$qSbEQjXIFqUugW{J$%SZ`_FmDD&*`>~FdN_2(hh%k`1 zrLe~|YCUc>?=3=_lLL{f9H$$hzmd0M>sYQ(ZE%$)AQibj+bt-JF|bW{h?OU$=(SYN z?HzteWL^&o7g~X*j^C7(`LVF92u9&egB_xE_wD_*4Bp-z72lDp)6+zlWd1 z&Q@#Y_1?arOamS*;73cbUD2h1$-&yd|Dhuby5kC-+lI4w?^Gk&gj#D! z+-&W!OApv@C5#pq~#&Ch2D`crYXl;3Z;18M2pv(DGP`B53E2w%5=PK(RHVPY- zznR(eh+Tf-$2TpQr;J(*;Q}|`~9{a&h7TA1@zN|h2M^e{Kw)QpM)}i`F1Z=m1DgZ3VHmGiDjfu71>-N znxM~nY(Sm;o-3*#Q#Y-r3p2$P0%VHoXCPp}j751ui8&qEM1Ff?F(PQ$VsW#n=ygqD z>eU*!t+rihLXLd-#IlV8Z5(}iJD1dIf6sNS#Yvct6@_gthO>Pe>kH>4p{(uN{&VZn zPy`X2{J(F#oVE)Jqv`?xV(Rh{nUIAYXqX}6b#Rd9>!vo_5hB~HQtDz(tX2_SU#)f{ zWwCbFhu&tp3tL*VJ)XAeGjefS@6>@pgWGpoArh9@fIUp)sA@;i=scOd+5!*4Bb%sT zIc81aK`qmyU-dV{1?1yTHC7rDL|ytjKx_a)QDT$JXQ9&^kSf>NldZ{y2{mfdF&pb~ zugMWMwQ8@red)B`4tJsrys)b=*J4Z5jF=sjX&*F?T@BfNZ4n*YesjF4@dUOT*K=|B zeDnmwlIH>jnFt^-e6gC|81eTm;a_L44QMcop@Vq!R7H&f3hWQSx!iciS-q0Srp8d^`Ku>`K zyaG47~%D3 zfL;K;L;92olG104K97anvE}S=(Op<8aJy}WQF0GY2WM`x+|QX$NQ1w>`^TQ29uWG+ z0A~WQNzZ6{L=O}NKfS>TCr6sb@=&~{;0?`4-5-Kban76SnOXPBA{UvK%EhO0UT=(N z>ib^~hdyhBhk4ZBQc&S2Zj@jT4CbLrWfZ{G##|VZ8%JueiRs%;T zoL!%AP#c&NBeUd~`xPkb$&@Rt`Fu^!ol|sf_p#7n*e-|}fD0+XNgGZ;4ZSYz?zNFq z?{@GVQ;BU|9hZKwc$93q9w=zL|G2libk+a! zfB*0Qslor@@PBCo6pao*k_Sk56u%5hzhyvbz|J%-l}8{)q(#(?_nvv{zFXh4-^)_C zKcN;62K67w2Xy^Fb7u&m7XbvRqap6y`oygYboxz^`w zrP~nCK3@TU(OsQAb`x%>M!PW_^GkJCT^OJwU6xPZP<+uy9YTCzC_-v?cn3-kR_I+NBd@ z9f8DRMCkC<3I#0FNxAQZ71UH|9at2LPf!3FpPoSu9<+W7;c>Xsy{Nw5$<1<$odlbu z>kI?Jt+xBqwD$Y%WOaE5=%vr49>B;iBICn#Lm2l}qqqQ?wb+^^quNtyM;>eHv_<*W zL9F0iV>{P(FQvR5*8GvGL$7r24~Ix7Yu!lrW%-#PBV9HMD)!o@IUvx{(ejTw?Pe{2XN@RUonF+ z@K*yj7Xer7I_6(Wb_C=u;_jC+3(9q}9(C15bGoUCm{FP^=3CI_tX*5u{IA`1 zuPPMTIPzT2Q)cXJEH1IK`)k%la#iCYjSAYk88RUm=R(^UAs((FjUy;e{=k$U^ZN>; zf3b()`ctl=hp(1i!)J;fRx`ct9K3FY<}9{pXMCXblfqasOx}V3S;p7l!;0N+N6+{-(aXG8TFoTUt z&)ptr^x6%gUSl~jubIb3gc&-Vh0O3`u9mdV_&vI^e946=?a?O+@%H!(ti9sYwwaxy zGTWYRRZtW^XmaH7bRw%V(axi`8^1lNBlk--brEE*eM2QbKk>hMbX1}uC)*4;oCZ3ROA;?e9Wzf3n%ek!yg8 z9N^N{b{u)saxo@C#VuEQRfk+RR6IWPcd{sG#BM`H)HaeQbyIOc7wV|B5HreT{nYbt zmo|vHAk(E88Id0tccZ2cEE4Nhn&|z;WWg-DF_-zl)?7Xz@+%}burH(9h>ih2vduHQ z45_gM+HAUeE-Zz4j-5w8zO{ECXwCOW;Q589EXaYo8dC{1dNFS9 zr!+saNkYS?brYo7iLJV(V`qX_XKK_|x#nQCIMJ1eW^}8q3nYk+a+3u);xVz)q>kb+ zS~EDPT!+^exhZ{bkfwoaw)ug)YDVb2IKI&Q2vz74z*0dI35A@>&8FUCR->q?MeTEV zXtk*IXi}ZV424xo;&4YyJCo|lp6D%A9B)WFq65ZX#p{z^>uq$4>Q4QhB&e;S-Q~9Z zO()_Txrs?HqB!s9WL>hAt{U=&;`a*hp}g~Z3WXT@IfX)F#e+8e71n2goT!9V!}8iJ z;r*U;9LZGIQaZxKJkz7Tc-)n`3Q;~+ywyaV>QR3`i}~$zB{3(G8yU@gy(bU{YBL+n z=iB2Vs1Ar>=xeLrvs=KTE)@F_*1t>(&Oi zWjeKbxv$pzkyIww9lsP}Q(lb^K`R=9(&R_Er{N-j$y>8;P_yNk6c~<;=G7rq8L!)= z+G%hLl1W0mRCvVV`aV;?qS%3n5Xw<#lfJB%}!v}c$fXWutV5JZJTGJnDp!Lv( zkUDDYM5kKqxZcF&7B#Q$H+dqif{C(Gcp&kif?SGqWd-7V<>sh z-glaDuCn<^j0&-ED%Ej{ODB<|QQ3J`FFy2aw{FsQgmVonmlt5!x1ZYV zcM!?8g0fE#9;yso*m^&yQ;?s25Jq;daj3VgO`}QjHW8NjdJEJqrsvz0wd^dMy=&W| zyBR48Y5QJauIrlD7Y{RbI%aKe6J5`|#=IdtrFjvJwRrU)B^P<2y3M2}H>2QR zA&*_M&9L>)w|NNg1%H}{@C@cgQCGM~*cSB8Sf}Ai!sd39?ClN#Gnkyqji|a{;?b}9 z^MI+$@gR-KzcjaayEV{9{#+m_dj~;^$CqPXY8Aor?+1HxX)g3$i2s#AaO+=m+Y{6a zs<3GQ3WE$g6{@oij)SEn_tlYs*X;gc%dLf9OJzf?T)}ne3r|6vb<=*3Me>BC1=6I$ z{04_v+gsS8LI_NN+)$W2esa3xxBhyiZY`Az;3mlHH zPG7EWNT~2dToy{+1M9nhKuf+pAkR)AiwXO3tDrci*#h?z%SsPlud4Bw>>Wf}0v8Tb zO7;5%1MYfCL4MCGK*atyNS4aqMoWK!4Au{^TF_#oIID$MW`kjSzA9~2EV-KDUM$^PS>kR6`#y-94w?T5| zPl)DC2cgHH_+PnTpx94fTo;`@6?ed3U!dcwyI*vgn_ZPVY-(Eq+>jUAqw1jYYpDg6 z@}~9NjbQSGELh{SzI@Mpkc57IG{1rEtZD*jJv*^}=*BDe% zl0AXN@^TDr2zlY4#EN^rba)2(tEB?~*Cm6GfPxB&8tiLH$DYTod*-IIwkCbLv+3+Q zQ=`(Z{ETamE%&Xc_-R5tlClWIb-9V{2x+HKay|6M^;i0QUG4G%XE>uIazzvEPInS& zqh_l+Q)^3um@DcSH+s94Uz>YkwKUqzwt;H3x=X!Q2F=sDqh6;R?LiH`I6BtG6f^%p z5el@Hi{1C0G~h=56=^^SS7gD@pXgy-wanhsp~5M4o*nINGj4a{xiz5qyx{JO*^ujd zyjs5x6*l3T3mS2N?a`vHCZ$Hl9*^BY+R+XaOboB zOmL|QD0}iFJ>|vLce)v(^JP~G8gpRf5W;oM@G}slkl_lLGN9p>KrbYJMOT5d&!Sk4 zQK`^ZON)B<*A8UG`>{UW|4L&dIZgkKvb&zd-_syGp<~2Pw*t)%_W^xC-f+-EufQ%q zp|>e3T7Bv|dyCNf$C|!cN(O;f4X(N_VdY@ZY4_`8Wj(C1YBhO1qjj7@ht4I9hP1G9)*a+N*Ad-~>5;nOpk9xV}>plGfl^bQK%Yg^|yEgi9; z&4MythM=B6v9EQlH2B=ANsENM-F&5;&lY`(u43qgG>XJ{N#S6>bI?>vxpowU;ZmZGs zR9{da_Sb8U6~8PK!fsUqC+-qWFr3nEQ_{q)H}}KxVI^|iF%N1nkG6HAT&XQXXI2}E z%&@0vL2iuZBaDNjgu}ICK!T;)aRZ8{ha(4)R^Q(dzEAi}(D2XUzsGZK*0v<0z_@~U zOfK-i_FO)_-JaXb={qG*k704ALFm^YJ%F3h`P6%loYyhvO{+c?wL&Y zaerrT{Ej@TXn2b~%rS*6_tq{cP_1cK#g5FX8)52-kG0nr(Cr<5=|)Nt%)VadyLLo= z+QKWyyT3B~oBQt{HvNxfx=QpV$e{A502=WD2?#4dz=*{2_C9VaG+NWTuIP5Yfd{!S z`%OMMceF&ORX%wf8JRZUnv`tfM3n>S*s=568aHe{;pJ8JpdaQy{QZpkKHE44~xOe9^^Ig46f zDIeH#cNYhIL~u4#_T!U|?9V+A^X0fn+IEt2{R{|=y`J>G z4VNb_z)QZvpMSwp5NTPCCWOsFOdSQ*ZGQxrPe`>LlF9C9AgTx!5B14&~EM@bJ(IaB`sR+V+8@HKD%W9@NUV)ZCqe z#)+;|np#`a`eK5C4h7^oT2zM9eScJ97QIcC>d7FBH^*7^yqjQwc}M>bO0{2Z5WyU2 z*ca)h1mUi|F^d4UzB`1&g!42Ve6lH*b9e#?h%=a2Q_w48wzd7RMhpUfGa5y!@z`l& z1ivTSeg)J4=(hR`cd?1du{Pc40eKj+Rg)m-jcyZCjXez`K8*X}?#!Q;*rYmko9@uz z^7H0C+L}YE*govT{X7BUmtr|hw<7n@>PVj$pJxTS*#cw%gtL-i!XlWL*`8Sq{s#Vg zG;*_SoMcq-vkL@vWj!=s#{S;s%sRC={$e_#)md#iSYQG(6398@l-A{_v#6Y8cGsRr zBZ2bSIxfr7kTSTX-r7thP@D$uOzWE|L&X$7+BL}K%98ck$?J@V8T}V-@9;~fmg1~= zgPg~r*Fkqy&Hm>8`zK5OT~Vv&-TsRs%>@A3>7;ZTuw21>8M~b+Xj$5&PY0=Vs_R-Q zE;st)!D7!ly>ip4?v7k#wD1o0dbq2yV%?NSiw@~>ozZBr2rRNRTLEv)HV}gYyN`L} zR272eyn6s)xEw)ylYozidZ_4@YuCWa5ovxdD)$~>0CYV(fx{2vkOJ}#qZ`SKnXVvABW9QQz?~lj$Qtu4fmC?LGVS5Y5MwN-R zu9$SA$?3mm#bz0QX>FCsb_qg2(jqMS_kIW9QSql9@jHmy20(b|2vxWTLzB=1o^F<= zvcL!%WmzIYnU*kWPGh>dAZ)|K>anrWc)hmXl%*4YJmY;gkX9f;K-9f<+uOEn9^VVy zo)#}=YnkM!b$<7xlyc8E2OsF_W~Haol%d-A^Ye|mG+_N^n_iDFMTO(F!zq2D6~12H)E6`?GDi z)wWo&W%Bz@Da>=)eY8b)|NIq!&wq~g=DL`N9lXgVI3U}Sf`0`Y3*FmYy+<{SEdDfN zoq}^g_XA|0IA9m+0>%PV99>>En`8VmEF;=N_&^k-YPDtFOxsj)#d!?Zbg3qr_E&Nl-T??JgugA3Bi9YvkO-AW-MAZ zIJ7z7GJcX4k5*skhz_X)#0B%~*T9tX^jm%+jv;^x{lSlb@Sr7Bknph4rrfkkAQ-rh zqtF?ZR@>p+l}qC|Wct>@t19J@t_g>p2d=t|MGIl4Q=VY12+;Funxpa*aHnraV{DSDXNec3=n~AepAE)rt76O zNAdkc^P}xVpMcE`W-GFr*q6MbCfk{BPPT_OZpO@kAu97=GoRvlspZe27rbbmLn~F3 z6;ux{B$F@A-rtMjEO}BKXK;FiGWY>mj0Me+bG>J~5+)5IUUH8|rN$5g^4w`VWZvEO zk1z&e+wi3liE|+~u-cTdbCXII2#j2?mFUKTa^@&Tm!9DHNd7rtULYVmUp^yL7a~rm z<3^2D=d?L?qUEVR^Ic=^9ePnGoRv!hCRXWYA0(;@-iXi-wk*Uk7Ig)DMfV09Hi*_% zqk-+X?YMQ4=*gZG+V-m98MSJz3*n=U`lORQQZ(1ROt?>QM+_FXLMm0*f9#~X-fAK&~A>?>r*0xAU5HCF@BT;$jQ4UC!#>;%%1Mk9YUt5@eA z=aW0YGYVRzikhecmI1tyN6NrWp5me6D0u{f@bLHrT72QS6({%Y%HG)4^*ZbACsV!@ zEQp3Gw5D}Ko!Dwcv+R~{o>t@jap@VjNGmw7to5p?nbkAsQ&e)NF2mXLHiI4PmN9pH zBEL+_jm#eXVi(liq)Z;>e%{CC3CbOD2~>c$g8gi;uan@Y!hA4PoY32~%H@@DP-M@- z>ZiQq>ztJXX3C`(sNjEYV?pu&5N<1mtczwW#v&Ve-J4q@Z?+wUhm}Owl^!S=a9Z+* zUrSm8mqEax3bgq}8U`|NL3xCfJy}GWO|?0_R>?c2z9FIHJy1Ip=rs8DN*LVyvmey>s&3989d6qa8O^v}~v?nYvs7{Y!YnY;i%NJFXqP-cG+nrz~x+1Rf= zbG@-n$642@uSW5CDW4}m_Hwj&a92_swLz(JItcyB&K1c)Q|ysLt8Wr~b0>OxSsOM2 zV!v$gW2(0;H&(>&own~cfo|Mf(9TYfA$)V~_wT6p2IB$!0Ag`U#lBi&qc$IG8Qf^$ z4SyKN_AIt+hTm(yCJ-+q@+E-`B!tDR#L_p?AXy=hiMc8T3fZ4a!yAkTECPstkio3Q z)QZS0`}B0C1m+~>gR(IfbYg=KyL=@t#PXp{3@!&AzJTNB(rPJ-bkN!5sDZw^+Lvw~ zeC?@N#0|%A*gKzHf{n{wy%!pPlVb8n5Dz$qFpH<0fViF#HAvE66e<{c{(e2h2qzBB zom|OtVBe7NY_lcHPP#3hpqle zp;me9`ex1fc0DA1EFBUlu2~Z=z~vrb;bJZ8wYIak(tJ`1rqfx!ibpGL+3${ORGB_^ zrHwWbJc6(H%dy7=rEuX`THD%AdwzIkc~ulf4&NB=OibzVl@%G(2%?VvL~ULhKEEdg z*fR3R#~w62qyqE-$ZIfH5lI6r3q7<&rp~&ps@)P#eY|np9lA3vVowoi8uY9UZd^?& z2j5@da@$`ImGW{qmHn$DXuc#FC7pG|E-u0ls-tx6M%mH zx>dzxw&!ht;tT$JjJ88)>Fd79sN(rI5E$4L_~Q%Bm$4^OW!9iR z*J@Y|r_}4#+}`1r@^2*fAK;1ApsjPWa?&RC6(;-%vQloz%86$z9cfODmO<&Fq~&4S z=k+vD2gfFz^MvFrpoYnv_bCS`_kEES_i#!7VU;)0`1mGw-FW@>`J4OGlM;Ej3iwR0 zkAw=PU!g=;op@vPLcY8^7Dw?j$Y z)oOtxh1HU!c1}C2)9iCI!nNEeXs@E?L0#DMeY?AlrAW|6{p#?QM1LAFhE=P+_@r*k z_4>-QnFMzP5a6ApF0CG)HuB8|flgN-v&R4K9`kFYtndb1A;1l~eny65A@PoVHM++- zA8yIvsj1HUs=C|Z8pzp^iEf@Z<{3laWzn(r^->1H9S4GW{I!-f^dc0%+;cSVJ#oW> z-%pAA0S3U{!xJj^R+zy1hw5fejXEl;?T9`1bv4nv?m^@tXdj94<%Qlu~}?rz*bul8@5hC{A)oMM}%hPpMN8BvXCbXuFJW% z^?8px=mh&4YP$ltpzrz!1S*)f2m+xww%ePvakIMW5VR62l`hs@F3i*9OsJM<8LQ=x z9L5WNFgRi?%ZdSCKa7TiXT`H>s}uEwB|qpBfe_OJwIcALEdXO|>X38 z#1tgojm=Wv~lP(KIB<{7D=6mw5gPfM1dOea}op28k93W!<|MtGDNm*t|_ESEexM+WowQNC>3YTy%fQ|8sJ_%mk(ULXq|x3)a%qz#YG(f^N3^tUG< z;)c>e=#Q(1?MRvPJ|UfF1N?2GLV~boe+w4G1WiOQ@~6QcK{Yr}|Dwn4AL)w%U@`jg z&+t9{;rX4pst%b^b%PnqN*1^LWzP9iC2Y-cvUV8Tb+I*G^mIkJ)N5N?AG*kT+u4HJ z=2KcBYc^e@N#eMxIcJq*aHeYFnDG;22A={rJ`5VreZ75?m5H+MkuN`h#K?PG#$IeG z{egb=MwP7^beEp%I^9lvt$M)Rdkb#Ac;_U&;Gy6XbfW#jC4@fv&n4{!;r7BYwD@gH z%iAO(MD!&0XU+C$$yF7t?`+*O*=ShEc0usU+#)fC9ki@ke~e-{C63N6IC_OtE3-{Q zL3WgIu8o?j+UeA7DeBqQI^ae1B2R>kWUc3y8{esN)@qH*b zfvMY{PyNc^yxE{jJlJL&@I@~>6#UorqVNYprQV0NE0Zp1jtpElgrroDHY>HKS}!B z8lSNf&rG*yJVU?qv|kkLU6^(>s~A9^$WuqLb)Wp(&{>%AHHk+1@Il5tgM$n zQ$x4WYl%uQY;_lNW8dIFuNF^W!wS2fZs^m#J((H1woNWYeaH9CL$*nUf-ZOUwXsf25XelAH5XKSX=y;u{TnkzE{3AeJ>Y0K|+ z7t(0^BKqLpp7%Lpt7L{m_NVUDt*Uo2G zbT-jB0!={X$l=|dV8U7{{ids+GZ8G~`hLp>hD@{OvGjXCV8FM2T*X^*rNUQmZLnb` zSZCC4Gw3`z?WWOKSg*aoacV0_b3IU_ok+G%(XxH<(R$=Ytw_L|Ra@$r+RPj2HQ0b= z>Bs|TGH;y%Z8be$wW0AgzF3Sgn|CFTR~`GUJ_|2%?_fPnZ)F2HUcn6nIvKy;0Q z^W0`<$f-}PH{b-?>9{)9no?!&G>)t}9t@fnhncjdZ2PEHcD;U^mQ3R)FBcWjn|VR+ zq&q`~JtLSz1wzm0V!AUcs5A7_aBLEZ1zHb=3e%z^1}>M9Hw| zm@9h$OlF43%yH?0o6_Y&a;M(sx1+^y>tpZ=f56TSXs_aiB_Q z91uyyXVr5&Zy1+1hrT8Z6C`{H|Mn!k@zAe*%}gtF06DGS;56{xvpe$&iSf9S&SnNK z;N{Gncze3YxrrYKK2hG?JpG?)%ARA%uPO8QGT_b~w@=uq{hz)Jcqn1piL+$VgnAa8 zKS`i`QuYu|TK&U2z*B}n_!2%Bv;p&4%fup{;sRv3q8saF{phm&Zm&TKh&QQAngi}k`OFEg-UW^=>pW%P7AVmeN&!bCPn1`p z5;$n#T{f1WEtmCW*cYj#P#MpWYi5%;s9DI0eO`d9!ZchZD| zx1s#=qA2@5Xi}e=`@e@m#oz&6 zXiX~H^PV_P7PVdnoZmG}Z8~;_2W;z*O>CpK1ZQV%SdMH?8Lop#++sLvO||N7zaz!` zk~evVU!pdM`AVOT5{n_Iy_5B2?%+z(S>gl!e~88z&{z6qZvKq6hOpe8(UBE&oxVZu zH`aK=ox<=i7%>7rmj?!jKcT@KUwA&H1(Po34LZSI?N&$VVNzF@qvcj4xXoUY`9-5j zH@qQYO#cOG`>YnqiEfck4tc7qas?9lP#8CgCQlI)vuL?>=h{L+t;xFIU=K9DX}PlF z49-()He4{C>vt-v?Y1Fy+-W@;_9_@iH@ER{z_+YkYfnVP3F`}>e8?VM*R(lNxAbY- zZnJ^Tfn=^+4qfsLza*%|y6UY(P96bbF(;RIy`IRPm?@vpk zkGvz0W~pa8eRUAhPg<6RGH?JBW$wg5j?x~RC6IecqDkM_V!iGZ6PDcCsG+hnSK8vN zwIw8unGEI1&hB)y&5#qRBe@U*P$qmPw<_J;0<=16_xgi5-6QDT-ac5Qxy$Q+Pk&x$$@EaJ_EWrlC~EZ`U~q z+ZSN(8V!1lIIvOl-bV16901o%Q<(rIx ze7}(d&Wv9Z-iy*ppjZPnF2bNd=;?4f$aU=Nk?Ly@v~YoZ$J6yZ%5`921oVlvkFlWE zZGnRO0PwHfE5SlL>*K#wG>~=u5F}f8@3$Wyd!yMT{BJ6oCs=9LxjjJ4I%w%8T(WYW zE++%8*&cMXUT-#Dup>irM;B%Z3ad=cd-`;w&TNCxcNgyDjG89KQPcn&oFAS?Y{d2t z;Pm12a%l!Dg&8d?`|Z}>{GO%0T8YQWxL2`=r_gk4QeY*LKaMy*cn8dx)(3ZM^lV8W z%px9)S-fPh`2jLb+<5K+#h5%t*1W^jHe;wfD4EKd0;TPH>Sf*H_F{wEGHa|ks_JCe zUg6tARS-{$hIB!+>cTXdvOPH%7j!2aYGwl*_UCMGBb?YdOPxpc5!Nc@#3y#;{(KMd zfp;EP#dZg1g7?Myz`=){2>(Ei0GKqI!3`ikuoQOQlPa)53wI_d5_;jHaJ$=?EV>yw zO(LK*^7}iOt_0dfI37IPp5wM>F1zlf

nZ^wcLH@Wr1lfddI;tHf(V&N98)aMu^X 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 + + + + + + + + + + Interlanguage Checker | FWDekker + + + + + + + + +

+ +
+
+

Interlanguage Checker

+ +
+

Check the consistency of MediaWiki interlanguage links in a simple overview.

+
+
+
+ + + +
+ +
+
+
+ + + + + +
+ +
+
+
+
+ + +
+
+
+
+
+
+
+
+ + + + +
+ + + + + + + diff --git a/src/main/js/main.js b/src/main/js/main.js new file mode 100644 index 0000000..116918e --- /dev/null +++ b/src/main/js/main.js @@ -0,0 +1,780 @@ +import fetchJsonp from "fetch-jsonp"; + + +/** + * A data class for combining a language and page title to identify a page. + * + * @property lang {string} the language of the wiki this page is of + * @property title {string} the title of the page + */ +class InterlangLink { + /** + * Constructs a new `InterlangLink`. + * + * @param lang {string} the language of the wiki this page is of + * @param title {string} the title of the page + */ + constructor(lang, title) { + this.lang = lang; + this.title = title; + } + + + /** + * Returns `true` if and only if the given object equals this `InterlangLink`. + * + * @param other {*} the object to compare to this `InterlangLink` + * @returns {boolean} `true` if and only if the given object equals this `InterlangLink` + */ + equals(other) { + return other instanceof InterlangLink && this.lang === other.lang && this.title === other.title; + } + + /** + * Converts this `InterlangLink` to a string. + * + * @returns {string} the string representation of this `InterlangLink` + */ + toString() { + return `${this.lang}:${this.title}`; + } + + /** + * Converts a string obtained from the `#toString` method back into an `InterlangLink`. + * + * @param string {string} the string to convert to an `InterlangLink` + * @returns {InterlangLink} the `InterlangLink` obtained from the given string + */ + static fromString(string) { + const parts = string.split(/:(.+)/); + return new InterlangLink(parts[0], parts[1]); + } +} + +/** + * A map of interwiki links. + * + * @property map {Object.} maps interwiki abbreviations to URLs + */ +class InterwikiMap { + /** + * Constructs a new interwiki map. + * + * @param map {Object.} the mapping from interwiki abbreviations to URLs to store in this map + */ + constructor(map) { + this.map = Object.assign({}, map); + } + + + /** + * Returns the URL for the given abbreviation, or `undefined` if the abbreviation could not be found. + * + * @param abbr {string} the abbreviation to return the URL of + * @returns {string} the URL for the given abbreviation, or `undefined` if the abbreviation could not be found + */ + getUrl(abbr) { + return this.map[abbr]; + } + + /** + * Returns `true` if and only if this map has a URL for the given abbreviation. + * + * @param abbr {string} the abbreviation to check for + * @returns {boolean} `true` if and only if this map has a URL for the given abbreviation + */ + hasUrl(abbr) { + return this.map[abbr] !== undefined; + } + + /** + * Returns a new `InterwikiMap` with all entries from both this map and the given map. + * + * @param other {InterwikiMap} the map to merge with + * @return a new `InterwikiMap` with all entries from both this map and the given map + */ + mergeWith(other) { + const conflicts = Object.keys(this.map) + .filter(key => other.map.hasOwnProperty(key)) + .filter(key => this.map[key] !== other.map[key]); + if (conflicts.length !== 0) + console.warn("iw map merge conflict(s)", conflicts); + + return new InterwikiMap(Object.assign({}, this.map, other.map)); + } +} + + +/** + * Interacts with the API in an asynchronous manner. + * + * @property baseUrl {string} the origin of the wiki's API + * @property apiPath {string} the path relative to the wiki's API; starts with a `/` + */ +class MediaWiki { + /** + * Constructs a new MediaWiki object. + * + * @param apiUrl the url to the `api.php` file + */ + constructor(apiUrl) { + const urlObj = new URL(apiUrl); + this.origin = urlObj.origin; + this.apiPath = urlObj.pathname; + } + + + /** + * Sends a request to the MediaWiki API and runs the given callback on the response. + * + * @param params {Object} the parameters to send to the API + * @return {Promise} the API's response + */ + request(params) { + console.debug(`Requesting from ${this.origin}${this.apiPath} with params`, params); + return fetchJsonp(this.origin + this.apiPath + "?format=json&" + new URLSearchParams(params).toString()) + .then(it => it.json()) + .catch(() => { + throw new Error("Could not to connect to API. Is the URL correct?") + }); + } + + /** + * Requests all language links on the given pages. + * + * @param pages {string[]} an array of pages to return links of + * @param [limit] {number|"max"} the maximum number of links to returns over all pages, between 1 and 5000 + * (inclusive); or "max" for the maximum + * @return {Promise>} the language links per page, which are + * `undefined` if the page could not be found + */ + getLangLinks(pages, limit) { + if (limit === undefined) limit = "max"; + + return this + .request({action: "query", prop: "langlinks", titles: pages.join("|"), lllimit: limit}) + .then(response => response.query.pages) + .then(pages => + Object.keys(pages).reduce((links, key) => { + let langlinks = undefined; + if (key >= 0) + langlinks = (pages[key].langlinks || []).map(it => new InterlangLink(it.lang, it["*"])); + + links[pages[key].title] = langlinks; + return links; + }, {}) + ); + } + + /** + * Returns this wiki's general information. + * + * @return {Object} this wiki's general information + */ + getGeneralInfo() { + return this.request({action: "query", meta: "siteinfo", siprop: "general"}) + .then(response => response.query.general); + } + + /** + * Requests this wiki's interwiki map. + * + * @return {Promise} this wiki's interwiki map + */ + getIwMap() { + return this + .request({action: "query", meta: "siteinfo", siprop: "interwikimap"}) + .then(response => + response.query.interwikimap.reduce((map, mapping) => { + map[mapping["prefix"]] = mapping["url"]; + return map; + }, {}) + ) + .then(map => new InterwikiMap(map)); + } +} + +/** + * Manages a `MediaWiki` instance for different languages, caching retrieved information for re-use. + * + * @property mws {Object.} the cached `MediaWiki` instances + * @property articlePath {string} the path to articles, where `$1` indicates the article name + * @property apiPath {string} the path to `api.php` + * @property baseLang {string} the language of the base `MediaWiki`, where the exploration starts + */ +class MediaWikiManager { + /** + * Constructs a new `MediaWikiManager`. + * + * The `#init` method **must** be called before invoking any other function. Behavior is undefined otherwise. + */ + constructor() { + this.mws = {}; + this._iwMap = new InterwikiMap({}); + } + + /** + * Initializes this `MediaWikiManager`. + * + * @param baseMw {MediaWiki} the `MediaWiki` that is used as a starting point + * @return {MediaWikiManager} this `MediaWikiManager` + */ + async init(baseMw) { + const general = await baseMw.getGeneralInfo(); + + this.articlePath = "" + general.articlepath; + this.apiPath = baseMw.apiPath; + this.baseLang = general.lang; + + this.mws[general.lang] = baseMw; + await this._importIwMap(baseMw); + + return this; + } + + + /** + * Returns the `MediaWiki` for the given language, creating it if necessary, or `undefined` if it it could not + * be created. + * + * @param lang {string} the language of the `MediaWiki` to return + * @returns {MediaWiki} the `MediaWiki` for the given language, or `undefined` if it could not be created + */ + async getMw(lang) { + if (this.hasMw(lang)) + return this.mws[lang]; + + if (!this._iwMap.hasUrl(lang)) + return undefined; + + const url = this._iwMap.getUrl(lang); + this.mws[lang] = new MediaWiki(url.slice(0, -this.articlePath.length) + this.apiPath); + await this._importIwMap(this.mws[lang]); + + return this.mws[lang]; + } + + /** + * Returns `true` if and only if this manager has a `MediaWiki` for the given language. + * + * @param lang {string} the language of the `MediaWiki` to check presence of + * @returns {boolean} `true` if and only if this manager has a `MediaWiki` for the given language + */ + hasMw(lang) { + return this.mws[lang] !== undefined; + } + + /** + * Imports the interwiki map from the given wiki, fetching it from the server and merging it into this manager's + * main interwiki map. + * + * @param mw {MediaWiki} the `MediaWiki` to import the interwiki map of + */ + async _importIwMap(mw) { + this._iwMap = this._iwMap.mergeWith(await mw.getIwMap()); + } + + /** + * Returns the article paths of all `MediaWiki`s known to this manager. + * + * @returns {Object.} the article paths of all languages known to this manager + */ + getArticlePaths() { + return Object.keys(this.mws).reduce((paths, lang) => { + paths[lang] = this._iwMap.getUrl(lang).slice(0, -2); + return paths; + }, {}); + } +} + +/** + * A network of interlanguage links. + */ +class InterlangNetwork { + /** + * Constructs a new `InterlangNetwork`. + * + * @param mappings {Object.} a mapping from source page to all pages it links to + * @param missing {InterlangLink[]} list of articles that do not exist + * @param articlePaths = {Object.} the article paths of each language + */ + constructor(mappings, missing, articlePaths) { + this._mappings = mappings; + this._missing = missing; + this._articlePaths = articlePaths; + } + + + /** + * Returns an appropriate label for the given link. + * + * The label itself is an `a` `HTMLElement` linking to the editor for the given link. + * The text in the label is just the language of the link if the given link is the only link with that language + * in this network, or the entire link otherwise. + * + * @param linkStr {string} the link to generate a label of + * @return {HTMLElement} an appropriate label for the given link + */ + _generateLabel(linkStr) { + const link = InterlangLink.fromString(linkStr); + const url = this._articlePaths[link.lang] + link.title; + + const span = document.createElement("span"); + if (this._missing.some(it => it.equals(link))) + span.classList.add("redLink"); + + const label = document.createElement("a"); + label.href = url; + label.target = "_blank"; + label.title = linkStr; + label.innerText = + Object.keys(this._mappings).filter(it => link.lang === InterlangLink.fromString(it).lang).length > 1 + ? linkStr + : link.lang; + span.appendChild(label); + + const padding = document.createElement("span"); + padding.innerHTML = " "; + span.appendChild(padding); + + const editIconLink = document.createElement("a"); + editIconLink.href = url + "?action=edit"; + editIconLink.target = "_blank"; + editIconLink.title = "Edit"; + new FontIcon("pencil").appendTo(editIconLink); + span.appendChild(editIconLink); + + span.appendChild(padding.cloneNode(true)); + + const copyIconLink = document.createElement("a"); + copyIconLink.href = "#"; + copyIconLink.title = "Copy"; + const copyIcon = new FontIcon("clipboard").appendTo(copyIconLink); + copyIconLink.addEventListener("click", () => { + navigator.clipboard.writeText(`[[${linkStr}]]`); + copyIcon.changeTo("check", 1000); + }); + span.appendChild(copyIconLink); + + return span; + } + + /** + * Generates the head of the table generated by `#toTable`. + * + * @return {HTMLElement} the head of the table generated by `#toTable` + */ + _generateTableHead() { + const head = document.createElement("thead"); + + const topRow = document.createElement("tr"); + const srcHead = document.createElement("th"); + srcHead.innerText = "Source"; + srcHead.rowSpan = 2; + srcHead.classList.add("sourceLabel"); + topRow.appendChild(srcHead); + + const dstHead = document.createElement("th"); + dstHead.innerText = "Destination"; + dstHead.colSpan = Object.keys(this._mappings).length; + topRow.appendChild(dstHead); + head.appendChild(topRow); + + const bottomRow = document.createElement("tr"); + Object.keys(this._mappings).sort().forEach(key => { + const cell = document.createElement("th"); + cell.appendChild(this._generateLabel(key)); + bottomRow.appendChild(cell); + }); + head.appendChild(bottomRow); + + return head; + } + + /** + * Generates the body of the table generated by `#toTable`. + * + * @return {HTMLElement} the body of the table generated by `#toTable` + */ + _generateTableBody() { + const body = document.createElement("tbody"); + + Object.keys(this._mappings).sort().forEach(srcKey => { + const row = document.createElement("tr"); + + const label = document.createElement("th"); + label.appendChild(this._generateLabel(srcKey)); + label.classList.add("sourceLabel"); + row.appendChild(label); + + Object.keys(this._mappings).sort().forEach(dstKey => { + const cell = document.createElement("td"); + if (srcKey !== dstKey) { + const isPresent = this._mappings[srcKey].some(it => it.equals(InterlangLink.fromString(dstKey))); + cell.innerText = isPresent ? "✓" : "✗"; + cell.classList.add(isPresent ? "correct" : "incorrect"); + } + row.appendChild(cell); + }); + + body.appendChild(row); + }); + + return body; + } + + /** + * Generates an HTML table describing the interlanguage network. + * + * @return {HTMLElement} the generated table + */ + toTable() { + const table = document.createElement("table"); + table.appendChild(this._generateTableHead()); + table.appendChild(this._generateTableBody()); + return table; + } + + + /** + * Discovers the interlanguage network, starting from the given link. + * + * @param mwm {MediaWikiManager} the manager to use for caching and resolving pages + * @param title {string} the title of the page to start traversing at + * @param [progressCb] {function(Object[]): void} a function handling progress updates + * @returns {Promise} a network of interlanguage links + */ + static async discoverNetwork(mwm, title, progressCb) { + const mappings = {}; + const missing = []; + + const history = []; + const queue = [new InterlangLink(mwm.baseLang, title)]; + while (queue.length > 0) { + const next = queue.pop(); + if (history.some(it => it.equals(next))) + continue; + + progressCb("Checking", next); + history.push(next); + const nextMw = await mwm.getMw(next.lang); + await nextMw + .getLangLinks([next.title]) + .then(langlinks => langlinks[next.title]) + .then(langlinks => { + mappings[next.toString()] = []; + if (langlinks === undefined) { + missing.push(next); + return; + } + if (langlinks.length === 0) + return; + + mappings[next.toString()] = langlinks; + queue.push(...langlinks); + }); + } + + return new InterlangNetwork(mappings, missing, mwm.getArticlePaths()); + } +} + + +/** + * Interacts with the DOM to delegate messages to the user. + */ +class MessageHandler { + /** + * Constructs a new `MessageHandler`, inserting relevant new elements into the DOM to interact with. + * + * @param parent {HTMLElement} the element to insert elements into + * @param [id] {string} the id of the div containing the message + */ + constructor(parent, id) { + this._mainDiv = document.createElement("div"); + this._mainDiv.style.display = "none"; + this._mainDiv.classList.add("messageInner"); + parent.appendChild(this._mainDiv); + + this._loadingIcon = document.createElement("div"); + this._loadingIcon.classList.add("lds-dual-ring"); + this._mainDiv.appendChild(this._loadingIcon); + + this._spacing = document.createElement("span"); + this._spacing.innerHTML = " "; + this._mainDiv.appendChild(this._spacing); + + this._textSpan = document.createElement("span"); + if (id !== undefined) + this._textSpan.id = id; + this._mainDiv.appendChild(this._textSpan); + + this._callback = undefined; + } + + + /** + * Handles the displaying of the given messages. + * + * If no messages are given, the current message and the loading icon are hidden. To display an empty message + * next to the loading icon, give an empty string. + * + * @param messages {...*} the messages to display, or no messages if the current message should be hidden + * @returns {MessageHandler} this `MessageHandler` + */ + handle(...messages) { + if (messages.length === 0) { + this._mainDiv.style.display = "none"; + return this; + } + + if (this._callback !== undefined) + this._callback(...messages); + + this._textSpan.innerText = messages.join(" "); + this._mainDiv.style.display = "initial"; + + return this; + } + + /** + * Calls `#handle` without any arguments. + * + * @returns {MessageHandler} this `MessageHandler` + */ + clear() { + return this.handle(); + } + + /** + * Sets the callback to be executed whenever messages are handler by this handler. + * + * @param callback {function(*[]): *} the function to execute whenever messages are handled + * @returns {MessageHandler} this `MessageHandler` + */ + setCallback(callback) { + this._callback = callback; + return this; + } + + /** + * Turns the loading icon on or off. + * + * @param state {boolean} `true` if and only if the loading icon should be on + * @returns {MessageHandler} this `MessageHandler` + */ + toggleLoadingIcon(state) { + this._loadingIcon.style.display = state ? undefined : "none"; + this._spacing.style.display = state ? undefined : "none"; + + return this; + } +} + +/** + * An input that can be validated. + * + * @property input {HTMLElement} the input that is validatable + */ +class ValidatableInput { + /** + * Constructs a new validatable input. + * + * @param input {HTMLInputElement} the input that is validatable + * @param isValid {function(string): string} returns an empty string if the given input string is valid, and a + * string explaining why it is is invalid otherwise + */ + constructor(input, isValid) { + this.input = input; + this._isValid = isValid; + } + + + /** + * Returns the value of the underlying input element. + * + * @return {string} the value of the underlying input element + */ + getValue() { + return this.input.value; + } + + /** + * Validates the input. + * + * @return an empty string if the input string is valid, and a string explaining why it is is invalid otherwise + */ + validate() { + const validity = this._isValid(this.input.value); + + if (validity.length === 0) this.showSuccess(); + else this.showError(); + + return validity; + } + + /** + * Marks the input as neither valid nor invalid. + */ + showBlank() { + this.input.dataset["entered"] = "false"; + this.input.setCustomValidity(""); + } + + /** + * Marks the input as invalid and moves focus to it. + */ + showError() { + this.input.dataset["entered"] = "true"; + this.input.setCustomValidity("Incorrect"); + this.input.focus(); + } + + /** + * Marks the input as valid. + */ + showSuccess() { + this.input.dataset["entered"] = "true"; + this.input.setCustomValidity(""); + } +} + + +/** + * A font-based icon. + */ +class FontIcon { + /** + * Constructs a new `FontIcon`. + * + * @param name {string} the name of the icon + */ + constructor(name) { + this._node = document.createElement("i"); + this._node.classList.add("fa", `fa-${name}`); + + this._name = name; + } + + + /** + * Appends this icon to the given parent node. + * + * @param parent {HTMLElement} the node to append the icon to + * @return {FontIcon} this `FontIcon` + */ + appendTo(parent) { + parent.appendChild(this._node); + return this; + } + + /** + * Temporarily changes this icon to the given name. + * + * @param name {string} the temporary icon to display + * @param time {number} the number of milliseconds to display the other icon + */ + changeTo(name, time) { + this._node.classList.remove(`fa-${this._name}`); + this._node.classList.add(`fa-${name}`); + + setTimeout(() => { + this._node.classList.remove(`fa-${name}`); + this._node.classList.add(`fa-${this._name}`); + }, time); + } +} + + +// noinspection JSUnresolvedFunction +doAfterLoad(async () => { + const urlInput = new ValidatableInput($("#url"), (value) => { + if (value.trim() === "") + return "URL must not be empty."; + + try { + new URL(value); // Throws exception if invalid + return ""; + } catch (error) { + return error.message; + } + }); + const pageInput = new ValidatableInput($("#page"), (value) => { + return value.trim() === "" ? "Page must not be empty" : ""; + }); + const checkButton = $("#check"); + const messages = $("#messages"); + + const progressHandler = new MessageHandler(messages) + .setCallback(() => errorHandler.clear()) + .toggleLoadingIcon(true); + const errorHandler = new MessageHandler(messages, "errorMessage") + .setCallback((...messages) => { + progressHandler.clear(); + console.error(...messages); + }) + .toggleLoadingIcon(false); + + + let previousUrl = undefined; + let mwm = undefined; + + const submit = async () => { + // Clean up + urlInput.showBlank(); + pageInput.showBlank(); + progressHandler.clear(); + errorHandler.clear(); + + const oldTable = $("#networkTable"); + if (oldTable !== null) + oldTable.parentNode.removeChild(oldTable); + + // Validate + const urlValidity = urlInput.validate(); + if (urlValidity !== "") return errorHandler.handle(urlValidity); + + const pageValidity = pageInput.validate(); + if (pageValidity !== "") return errorHandler.handle(pageValidity); + + // Initialize + if (urlInput.getValue() !== previousUrl) { + progressHandler.handle("Initializing", urlInput.getValue()); + + try { + const mw = new MediaWiki(urlInput.getValue()); + mwm = await new MediaWikiManager().init(mw); + } catch (error) { + errorHandler.handle(error); + return; + } + + previousUrl = urlInput.getValue(); + } + + // Discover + InterlangNetwork + .discoverNetwork(mwm, pageInput.getValue(), progressHandler.handle.bind(progressHandler)) + .then(it => { + progressHandler.handle("Creating table"); + + const newTable = it.toTable(); + newTable.id = "networkTable"; + + const form = $("#networkTableForm"); + form.textContent = ""; + form.appendChild(newTable); + + progressHandler.handle(); + }) + .catch(error => errorHandler.handle(error)); + }; + + urlInput.input.addEventListener("keypress", (event) => { + if (event.key.toLowerCase() === "enter") submit(); + }); + pageInput.input.addEventListener("keypress", (event) => { + if (event.key.toLowerCase() === "enter") submit(); + }); + checkButton.addEventListener("click", () => submit()); +});